若依分离版 Oracle 主从数据源配置及手动切换失败解决办法
前言
在使用若依框架进行多数据源开发时,经常会遇到需要操作多个数据库进行 CRUD 操作的需求。然而,在配置完成后可能会发现数据源切换不生效的问题。经过深入排查发现,这个问题通常与 @Transactional
注解的使用有关。
一、数据源配置
1. 配置从库数据源
在 application-druid.yml
文件中添加从库数据源配置:
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: true
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
2. DruidConfig 配置
在 DruidConfig
类中配置读取和添加数据源:
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
// 添加从库数据源
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
二、使用方法
1. 注解方式切换数据源
方法级别使用
在需要使用多数据源的方法上添加 @DataSource
注解:
@DataSource(value = DataSourceType.SLAVE)
public List<SysUser> selectUserList(SysUser user) {
return userMapper.selectUserList(user);
}
类级别使用
在整个 Service 类上添加注解,该类的所有方法都会使用指定数据源:
@Service
@DataSource(value = DataSourceType.SLAVE)
public class SysUserServiceImpl {
// 类中所有方法都使用从库
}
2. 手动切换数据源
通过编程方式手动切换数据源:
public List<SysUser> selectUserList(SysUser user) {
// 手动设置数据源
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
try {
List<SysUser> userList = userMapper.selectUserList(user);
return userList;
} finally {
// 清除数据源设置
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
三、切换不生效问题分析与解决
问题现象
使用手动切换数据源的方法时,代码运行过程中始终操作的是同一个数据库,数据源切换不生效。
问题根因
经过排查发现,问题主要是由于添加了 @Transactional(rollbackFor = Exception.class)
注解导致的。这与 Spring 的事务管理机制密切相关:
1. 事务范围内的数据源切换限制
当方法被 @Transactional
注解标记时,Spring 会为该方法创建一个事务上下文。如果事务管理器配置为在同一个事务范围内使用相同的数据源,则后续的数据源切换操作可能会被忽略。
2. 默认事务管理器的配置限制
Spring 默认的事务管理器(如 DataSourceTransactionManager
)可能会限制数据源切换的作用范围,导致事务管理器忽略在方法中手动设置的数据源切换操作。
3. 事务代理的影响
使用 @Transactional
注解时,Spring 会在运行时动态创建代理对象来管理事务。这可能导致在方法调用时,数据源切换操作被代理对象拦截或忽略。
解决方案
方案一:避免使用 @Transactional 注解
// 不使用 @Transactional 注解,手动管理事务
public List<SysUser> selectUserList(SysUser user) {
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
try {
List<SysUser> userList = userMapper.selectUserList(user);
return userList;
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
方案二:使用 AspectJ 切面管理事务
考虑使用 AspectJ 切面来管理事务,以绕过 Spring 默认的代理机制:
@Aspect
@Component
public class DataSourceTransactionAspect {
@Around("@annotation(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
String dataSourceKey = dataSource.value().name();
DynamicDataSourceContextHolder.setDataSourceType(dataSourceKey);
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
方案三:优化事务管理器和数据源配置
检查并优化事务管理器和数据源配置,确保它们与数据源切换需求一致:
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
四、最佳实践建议
1. 数据源切换原则
- 读写分离:查询操作使用从库,写入操作使用主库
- 业务隔离:不同业务模块使用不同数据源
- 异常处理:确保数据源切换后能够正确清理
2. 注意事项
- 在使用手动切换时,必须在
finally
块中清理数据源设置 - 避免在同一个事务中切换多个数据源
- 合理设计数据源切换的颗粒度,避免频繁切换
3. 调试技巧
// 添加日志输出,便于调试数据源切换情况
public List<SysUser> selectUserList(SysUser user) {
String currentDataSource = DynamicDataSourceContextHolder.getDataSourceType();
log.info("Current DataSource: {}", currentDataSource);
DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
log.info("Switched to DataSource: {}", DataSourceType.SLAVE.name());
try {
return userMapper.selectUserList(user);
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
log.info("DataSource cleared");
}
}
总结
若依框架的多数据源切换功能强大且灵活,但在实际使用中需要注意事务管理对数据源切换的影响。通过合理的配置和正确的使用方式,可以有效解决数据源切换不生效的问题,实现稳定可靠的多数据源操作。