DynamicDataSource是如何解决多数据源的事务问题?

多数据源的真面目DynamicRoutingDataSource

DynamicRoutingDataSource是什么?

该类实现了DataSource接口并在内部维护了一个map,其中存放了多个真实的DataSource,而key则是不同数据源的名称,本质上就是基于静态代理模式代理了多个真实的数据源对象

在用多数据源时可以使用@DS来切换不同的数据源,这依赖于DynamicRoutingDataSource。要完成这个功能需要@DS+DSProcessor+ThreadLocal+AopMethodInterceptor+DynamicRoutingDataSource#getConnection相互配合才能成功切换数据源。

多数据源下的事务会有什么问题?

在没有多数据源时,系统中一般只会存在一个Datasource,所谓的HikariDataSource,DruidDatasourceCommonsDbcp2PoolDataSource都是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注解的切面。分别是dynamicDatasourceAnnotationAdvisordynamicTransactionAdvisor

@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时都会执行回滚
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值