@Transactional和@DS怎样在事务中切换数据源

 更新时间:2023年07月31日 09:47:17   作者:Abstracted  
这篇文章主要介绍了@Transactional和@DS怎样在事务中切换数据源问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

@Transactional和@DS在事务中切换数据源

在一次需求中,需要对两个数据库进行读写操作,并且要保证对这两个库的操作的原子性。

所以就在一个service方法中加入了@Transaction,方法中包含了对两个库的操作。

spring:
  datasource:
    dynamic:
      primary: A 
      datasource:
        A:
          url:..
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
        B:
          url: ..
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
        C:
          url: ..
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
@Mapper
@DS("A")
public interface AMapper{
      @Insert("insert into a ...")
      void save();
}
@Mapper
@DS("B")
public interface BMapper{
      @Insert("insert into b ...")
      void save();
}
public class aService{
@Autowired
private aMapper、bMapper、cMapper
    @Transactional
    public void save(){
	    aMapper.save();
	    bMapper.save(); #报错
    }    
}

在执行测试时bMapper.save()发生了错误,报错信息:找不到b表,很明显sql是从A库中找b表,当然会报错找不到b表。

如果把@Transactional注解去掉就可以正常运行,数据也成功分别写入两个库中。

但是我们命名已经在2个Mapper上加了@Ds()注解来切换要是用的数据源,那为什么加入了事务就报错了呢?

@Transactional执行流程

1、save方法添加了 @Transactional 注解,Spring 事务就会生效。此时,Spring TransactionInterceptor 会通过 AOP 拦截该方法,创建事务。而创建事务,势必就会获得数据源。

  • 那么,TransactionInterceptor 会使用 Spring DataSourceTransactionManager 创建事务,并将事务信息通过 ThreadLocal 绑定在当前线程。
  • 而事务信息,就包括事务对应的 Connection 连接。那也就意味着,还没走到 OrderMapper 的查询操作,Connection 就已经被创建出来了。
  • 并且,因为事务信息会和当前线程绑定在一起,在 OrderMapper 在查询操作需要获得 Connection 时,就直接拿到当前线程绑定的 Connection ,而不是 OrderMapper 添加 @DS 注解所对应的 DataSource 所对应的 Connection 。

2、OK ,那么我们现在可以把问题聚焦到 DataSourceTransactionManager 是怎么获取 DataSource 从而获得 Connection 的了。

  • 对于每个 DataSourceTransactionManager 数据库事务管理器,创建时都会传入其需要管理的 DataSource 数据源。
  • 在使用 dynamic-datasource-spring-boot-starter 时,它创建了一个 DynamicRoutingDataSource ,传入到 DataSourceTransactionManager 中。
  • DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说,本示例中就管理了 a、b两个数据源,并且默认使用 a 数据源。
  • 那么在当前场景下,DynamicRoutingDataSource 需要基于 @DS 获得数据源名,从而获得对应的 DataSource ,结果因为我们在 Service 方法上,并没有添加 @DS 注解,所以它只好返回默认数据源,也就是 a 。故此,就发生了 找不到表 的异常。

我们在上面了解到,因为@Transactional会创建事务然后获得数据源,因为我们service方法上没有@DS注解,就拿了默认数据源,并且在这之后,这个事务信息会通过threadLocal跟当前线程绑定,事务信息包括了connection连接,也就意味着,在进入这个service方法的时候,当前事务就绑定了数据源a,在运行到bMapper.save()时,因为connection已经存在,所以拿到的数据源还是a,这时候就找不到b库里的表了。

解决方法

把对B库操作的方法都加上@DS和 @Transactional注解(在service上加)。

ps:在完成了aMapper.save()之后去调用bMapper.save()时,一定要把@Transactional设置为Propagation.REQUIRES_NEW,这样在调用另一个事务方法时,TransactionInterceptor 会将原事务挂起,暂时性的将原事务信息和当前线程解绑,然后创建一个新的事务,并且从数据源中取出一个connection链接,而此时的数据源已经被切换成我们需要的数据源。如果B操作发生了异常,B事务将回滚并将异常进行抛出到A,A事务自然也会回滚。

在对B库的操作如果不需要事务的话,可以在对B库操作的方法上指定事务的隔离级别为NOT_SUPPORTED,这样执行到该方法时会暂停挂起当前事务,等待对B库的方法执行完毕再恢复事务。

pps:当前事务方法里用this来调用另一个事务方法时,当前这个事务方法的@DS也会起作用,原因是@Ds是基于AOP切面在方法执行前切换数据源,而this调用的不是通过代理生成的事务对象,而是自己本身的原对象,不会开启事务。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Mybatis中特殊SQL的执行

    Mybatis中特殊SQL的执行

    这篇文章主要介绍了Mybatis中特殊SQL的执行,介绍内容包括模糊查询、批量删除、动态设置表名、添加功能获取自增的主键等相关资料,需要的小伙伴可以参考一下
    2022-04-04
  • Java实现的时间戳与date对象相互转换功能示例

    Java实现的时间戳与date对象相互转换功能示例

    这篇文章主要介绍了Java实现的时间戳与date对象相互转换功能,结合具体实例形式分析了java日期与时间戳类型的表示与转换相关操作技巧,需要的朋友可以参考下
    2017-06-06
  • Java学习笔记之观察者模式

    Java学习笔记之观察者模式

    这篇文章主要为大家详细介绍了Java学习笔记之观察者模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Java框架Quartz中的Trigger简析

    Java框架Quartz中的Trigger简析

    这篇文章主要介绍了Java框架Quartz中的Trigger简析,所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性,这些属性,在构建trigger的时候可以通过TriggerBuilder设置,需要的朋友可以参考下
    2023-11-11
  • java虚拟机中多线程总结

    java虚拟机中多线程总结

    在本篇内容中小编给大家分享的是关于java虚拟机中多线程的知识点总结内容,需要的朋友们参考学习下。
    2019-06-06
  • Java通用Mapper UUID简单示例

    Java通用Mapper UUID简单示例

    今天小编就为大家分享一篇关于Java通用Mapper UUID简单示例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • Springboot整合Urule的方法步骤

    Springboot整合Urule的方法步骤

    这篇文章主要介绍了Springboot整合Urule的方法步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • Java 实战练手项目之校园超市管理系统的实现流程

    Java 实战练手项目之校园超市管理系统的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+Mysql+Maven+Bootstrap实现一个校园超市管理系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Spring Boot利用Lombok减少Java中样板代码的方法示例

    Spring Boot利用Lombok减少Java中样板代码的方法示例

    spring Boot是非常高效的开发框架,lombok是一套代码模板解决方案,将极大提升开发的效率,下面这篇文章主要给大家介绍了关于Spring Boot利用Lombok减少Java中样板代码的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • Java操作redis设置第二天凌晨过期的解决方案

    Java操作redis设置第二天凌晨过期的解决方案

    这篇文章主要介绍了Java操作redis设置第二天凌晨过期的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论