DynamicDataSource怎样解决多数据源的事务问题
作者:Abstracted
多数据源的真面目DynamicRoutingDataSource
DynamicRoutingDataSource是什么?
该类实现了DataSource接口并在内部维护了一个map,其中存放了多个真实的DataSource,而key则是不同数据源的名称,本质上就是基于静态代理模式代理了多个真实的数据源对象。
在用多数据源时可以使用@DS来切换不同的数据源,这依赖于DynamicRoutingDataSource
。
要完成这个功能需要
@DS+DSProcessor+ThreadLocal+AopMethodInterceptor+DynamicRoutingDataSource#getConnection
相互配合才能成功切换数据源。
多数据源下的事务会有什么问题?
在没有多数据源时,系统中一般只会存在一个Datasource
,所谓的HikariDataSource
,DruidDatasource
,CommonsDbcp2PoolDataSource
都是DataSource的实现,但是在系统中总是唯一存在的。因为SqlSessionTemplateFactory
中只能存在一个DataSource,SqlSessionTemplate又是通过SqlSessionTemplateFactory
生成的,并且SqlSessionTemplate
在容器中又只能存在一个,所以事务时不会出现混乱的。而DynamicRoutingDataSource
同样也是系统中的唯一的Datasource,只不过它内部代理了多个DataSource。
再来说说spring的事务管理器TransactionSynchronizationManager
,内部使用ThreadLocal保存当前事务信息。当程序执行到@Transactional
的AOP拦截器时,会在ThreadLocal中保存当前的事务信息,其中就包含了与数据库的Connection对象。在程序执行sql时,spring的SqlSessionTemplate#getSession
方法会从事务管理器中获取到与当前线程绑定的connection对象。
综上所述,当使用@DS切换数据源时,没有事务的情况下还好,会使用DynamicRoutingDataSource
获取一个新的connection并使用,但是如果是在事务的情况下,会使用事务管理器中获取与当前线程绑定的Connection,而这个connection则是事务被创建时获取的connection,就造成了虽然指定了数据源,但是还是原本的那个connection。导致切换数据源失败。
@Ds与@DsTransactional
通过观察DynamicDataSourceAutoConfiguration
自动配置类可以发现,DynamicDataSource默认自动配置了@Ds注解
及@DsTransactional
注解的切面。
分别是dynamicDatasourceAnnotationAdvisor
及dynamicTransactionAdvisor
。
@Ds的原理
其切面方法拦截器会将指定的ds名称存入DynamicDataSourceContextHolder
中的ThreadLocal<Deque<String>>
中,因为指定了ThreadLocalMap的泛型为Deque
,而Deque的作用更准确的说是栈的作用,所以支持方法嵌套调用时使用不同的ds名称。
@DsTransactional的原理
通过源码可以发现在没有使用seta分布式事务控制的情况下,多数据源的事务是通过dynamicDatasourceAnnotationAdvisor
管理的。
dynamicDatasourceAnnotationAdvisor
的核心切面拦截器就是DynamicLocalTransactionInterceptor
DynamicLocalTransactionInterceptor多数据源本地事务拦截器
内部实现原理非常简单,其实就是借助于ThreadLocal。
看到这里,你可能已经猜到了,面纱下面的真面目就是这个ConnectionFactory
类了。
我们再来看看他张了一幅什么面貌。
我们已经知道了notify方法是@DsTransactional
切面环绕通知结束时会被调用的,本着追溯本源的好奇心,你可能开始好奇了,putConnection
又是什么时候被调用的呢?
我们继续跟进就来到了AbstractRoutingDataSource
。
好!又回到了DynamicRoutingDataSource
中, AbstractRoutingDataSource
就是DynamicRoutingDataSource
父类。
在本地事务结束时,TransactionContext会清空本地事物的状态标识,然后分别结束每一个connection的事务状态。
然后清除ConnectionFactory中保存的与本次本地事物有关的所有connection对象的引用。
至此本地事物的创建和结束就完成了闭环。
总结一下
多数据源事务的控制中,参与的核心职责类有哪些
DynamicRoutingDataSource
: DataSource接口的实现,也是一个DataSource,本质是多个DataSource的静态代理类。自定义了getConnection方法的实现逻辑,使其与多数据源的事务管理紧密配合。TransactionContext
:使用ThreadLocal实现。本地事物上下文。负责管理本地事物的状态。ConnectionFactory
: 使用ThreadLocal实现。connection连接的静态代理类,也是一个委派者设计模式。负责管理当前本地事务中的所有Connection。ConnectionProxy
: 封装了真实的Connection。 目的是将ds数据源名称和connection对应起来,这样可以通过ds数据源名称拿到对应的connection。@Ds
:用于指定接下来要使用的数据源名称。@DsTransactional
: 多数据源事务的开启注解,用法同@Transactional相同。但是不能指定事务的一些属性,因为其实现的原理,也不需要任何其他的事务的配置。当发生任何Exception时都会执行回滚
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。