支持多数据源的 MyBatis-Plus 实现
1. 多数据源配置原理
在 Spring Boot 项目中使用 MyBatis-Plus 支持多数据源,通常需要基于 AbstractRoutingDataSource
实现动态数据源切换。其核心原理包括:
- 定义多个数据源:在
application.yml
或application.properties
配置不同的数据源信息。 - 创建数据源 Bean:基于
DataSourceBuilder
初始化不同的数据源实例。 - 使用 AbstractRoutingDataSource:继承
AbstractRoutingDataSource
,实现determineCurrentLookupKey
方法,根据上下文切换数据源。 - 配置 MyBatis-Plus 关联多数据源:设置
SqlSessionFactory
和DataSourceTransactionManager
关联不同的数据源。
示例配置:
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/db1
username: root
password: root
secondary:
url: jdbc:mysql://localhost:3306/db2
username: root
password: root
2. 动态切换数据源的实现
动态切换数据源的关键在于 AbstractRoutingDataSource
的使用和 AOP 方式实现方法级别的切换。
2.1 自定义数据源上下文管理
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dataSourceKey) {
CONTEXT_HOLDER.set(dataSourceKey);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clear() {
CONTEXT_HOLDER.remove();
}
}
2.2 继承 AbstractRoutingDataSource
实现动态路由
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
2.3 AOP 方式切换数据源
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object around(ProceedingJoinPoint point, TargetDataSource targetDataSource) throws Throwable {
try {
DataSourceContextHolder.setDataSource(targetDataSource.value());
return point.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
3. 数据源的管理与事务问题
多数据源事务管理主要涉及 同一数据源内事务保证 和 跨数据源事务处理。
3.1 单数据源事务管理
对于单个数据源,使用 @Transactional
即可正常管理事务。
@Transactional
public void updateUser(User user) {
userMapper.updateById(user);
}
3.2 跨数据源事务处理(分布式事务)
由于 @Transactional
只能保证单数据源事务,需要使用 XA 事务 或 TCC 分布式事务方案:
- XA 事务:使用 Atomikos、Seata 进行全局事务管理。
- TCC 事务:利用 Try-Confirm-Cancel 模型管理跨数据源事务。
示例:基于 Seata 进行分布式事务管理
@GlobalTransactional
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
accountService.decreaseBalance(fromUserId, amount);
accountService.increaseBalance(toUserId, amount);
}
4. 多数据源使用中的性能优化
在多数据源场景下,合理优化性能可以减少数据库开销,提高系统吞吐量。
4.1 连接池优化
使用 HikariCP 作为连接池,并合理调整参数,如 maximumPoolSize
、idleTimeout
、connectionTimeout
以减少连接创建开销。
示例配置:
spring:
datasource:
hikari:
maximumPoolSize: 20
idleTimeout: 30000
connectionTimeout: 3000
4.2 减少数据源切换
避免在单个事务中频繁切换数据源,可通过业务拆分减少跨库操作。
4.3 读写分离
采用 主从数据库架构,结合 MyBatis-Plus 的多数据源策略,将 写操作 路由到主库,读操作 路由到从库,提升查询性能。
示例:
@TargetDataSource("slave")
public List<User> getUsers() {
return userMapper.selectList(null);
}
5. @DS 注解与 AbstractRoutingDataSource 方式的对比
在 MyBatis-Plus 中,除了 AbstractRoutingDataSource 方式,还可以使用 @DS 注解指定数据源。
5.1 配置
如果使用 @DS 注解(Mybatis-Plus 自带的动态数据源切换),通常需要在 application.yml配置 dynamic,示例如下:
spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/db1
username: root
password: root
slave:
url: jdbc:mysql://localhost:3306/db2
username: root
password: root
dynamic关键字在这里的作用是 让Mybatis-Plus知道这是一个动态数据源配置,否则@DS 注解无法生效。
如果使用 AbstractRoutingDataSource方式,dynamic 并不是必须的,因为动态数据源的管理是由 AbstractRoutingDataSource及其 determineCurrentLookupKey()方法控制的,数据源的配置可以直接放在 spring.datasource下
5.2 @DS 注解应用
MyBatis-Plus 提供 @DS 注解,可以直接在方法或类级别指定数据源。
@DS("secondary")
@Service
public class OrderService {
public List<Order> getOrders() {
return orderMapper.selectList(null);
}
}
当 @DS 注解作用于类时,类中的所有方法都将默认使用该数据源,除非方法上另有 @DS 注解指定不同的数据源。
方式|适用场景|事务管理|代码侵入性
@DS 注解|方法或类级别的数据源切换|适用于单一方法调用,事务受限|低
AbstractRoutingDataSource|复杂的动态数据源路由|支持自定义事务策略|高
总结:
-
@DS 适用于简单的场景,方便快捷。
-
AbstractRoutingDataSource 适用于需要更灵活的数据源控制,如基于用户、请求等动态切换数据源。
通过上述配置和优化,我们可以在 MyBatis-Plus 中实现灵活的多数据源支持,同时保证系统的稳定性和高效性。