背景
操作多个数据库的数据时,需要去解决如何动态管理多个数据源以及切换的问题,并保证多数据源的事务一致性。
遇到的问题
Mybatis-plus—多数据源@DS和@Transactional冲突
当前项目已有自定义的事务配置,如下:
@Aspect
@Configuration
@Slf4j
public class TransactionConfig {
private static final int TX_METHOD_TIMEOUT = 300;
private static final String AOP_POINTCUT_EXPRESSION = "execution(*com.example..service..*Service.*(..))";
@Bean(name = "txAdvice")
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
// 只读事务,不做更新操作
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("create*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("batch*", requiredTx);
txMap.put("modify*", requiredTx);
txMap.put("delete*", requiredTx);
txMap.put("remove*", requiredTx);
txMap.put("exec*", requiredTx);
txMap.put("set*", requiredTx);
txMap.put("do*", requiredTx);
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
txMap.put("find*", readOnlyTx);
txMap.put("*", requiredTx);
source.setNameMap(txMap);
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager(), source);
return txAdvice;
}
@Bean
public Advisor txAdviceAdvisor(@Qualifier("txAdvice") TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
// 自定义事务管理器
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(Datasource datasource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(datasource);
return transactionManager;
}
}
- 因为spring在开启事务的同时,会去数据库连接池拿数据库连接。在最外层接口层中开启事务,即TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务,并将事务信息连接信息,通过 ThreadLocal 绑定在当前线程。
- 在内层使用@DS切换数据源,并没有重新开启新事务,没有改变当前线程事务的连接信息,仅仅是做了一次拦截,改变了DataSourceHolder的栈顶dataSource,对于整个事务的连接是没有影响的,所以会产生数据源没有切换的问题。
修复问题
原本的管理单个数据源,现在把动态路由数据源(MyRoutingDataSource)交给事务管理器。
@Autowired
private ConfigurableApplicationContext applicationContext;
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(applicationContext.getBean(MyRoutingDataSource.class));
return transactionManager;
}