@Transactional导致 dynamic-datasource-spring-boot-starter失效原因分析
环境:controller -->Aservice–>Bservice–>Bdao(A表示A数据源,B表示B数据源)
Aservcie使用Transactional注解
1、dynamic-datasource@DB切面是可以将数据源信息push到DynamicDataSourceContextHolder类的ThreadLocal<Deque> LOOKUP_KEY_HOLDER中
2、如果controller调用service,且controller -->Aservice–>Bservice–>Bdao(A表示A数据源,B表示B数据源),当且仅当Aservice使用@Transactional进行事物管理时,当controller -->Aservice,此时AbstractPlatformTransactionManager 类调用doBegin(transaction, definition)方法,将执行TransactionSynchronizationManager的ThreadLocal<Map<Object, Object>> resources 加入A数据源
3、AbstractPlatformTransactionManager 类获取事物时候将会判断事物是否包含数据源,如果有则认为该事物已经存在,将进行事物的类型判断,如果是PROPAGATION_REQUIRES_NEW类型,将创建新事物,新事物将拥有新的数据源持有Holder,默认的话则继承事物
4、Aservice–>Bservcie虽然可以实现1中所述,但是Bservcie–>Bdao时,DataSourceUtils执行doGetConnection时候,将会执行TransactionSynchronizationManager.getResource(dataSource)获取事物数据源A(源于2的结论),此时多数剧源形同虚设,失效,
方案:
1、Bservcie使用PROPAGATION_REQUIRES_NEW 类型事物,创建新事物 原因为上述3
2、使用Aservcie 的@Transactional替换成@DSTransactional注解即可,原因如下:
dynamic-datasource-spring-boot-starter启动时,进行DynamicDataSourceAutoConfiguration初始化,aop自定义事物,此时不走spring事物规则,将使用自定义事物,这个看起来很简单的事物规则,规避了复杂业务逻辑下的不便,代码如下:
@Slf4j
public class DynamicLocalTransactionAdvisor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (!StringUtils.isEmpty(TransactionContext.getXID())) {
return methodInvocation.proceed();
}
boolean state = true;
Object o;
String xid = UUID.randomUUID().toString();
TransactionContext.bind(xid);
try {
o = methodInvocation.proceed();
} catch (Exception e) {
state = false;
throw e;
} finally {
ConnectionFactory.notify(state);
TransactionContext.remove();
}
return o;
}
}