查看: 1103|回复: 1

解决:MyBatis-plus多数据源方法上方添加事务,数据源切换...

[复制链接]

2

主题

2

帖子

8

积分

个人用户

积分
8
发表于 2022-6-28 14:21:25 | 显示全部楼层 |阅读模式
本帖最后由 zengyoulin 于 2022-6-28 14:28 编辑

一、场景描述
项目当中使用的多数据源,Impl中有个方法:MethodA。
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableA")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5.     @Override
  6.     public R<?> MethodA(XXXX xxxx) {
  7.       
  8.     }
  9. }
复制代码
该方法中同时操作了两张表:tableA、tableB(tableA、tableB来自两个数据源)。
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableA")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5.   // ........
  6.   
  7.     @Override
  8.     public R<?> MethodA(XXXX xxxx) {
  9.       // 操作tableA的方法
  10.       operate1(xxxx);
  11.       // 操作tableB的方法
  12.       operate2(xxxx);
  13.     }
  14. }
复制代码
出于数据一致性考虑,楼主在MethodA上方加了 @Transactional(rollbackFor = Exception.class)
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableA")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5.    // ........

  6. @Override
  7.     @Transactional(rollbackFor = Exception.class)
  8.     public R<?> MethodA(XXXX xxxx) {
  9.       // 操作tableA的方法
  10.       operate1(xxxx);
  11.       // 操作tableB的方法
  12.       operate2(xxxx);
  13.     }
  14. }
复制代码
运行项目后发现,执行操作tableB的数据时,数据源并没有切换。
然而,去掉该事务注解后,数据源则切换正常。但不能保证数据一致性。这.....

二、相关疑问1、为什么在该方法上方加@Transactional(rollbackFor = Exception.class)注解会导致切换数据源失败?
因为在开启事务的同时,会从数据库连接池获取数据库连接。内层的service虽然使用了@DS切换数据源,但实质上并没有改变整个事务的连接。而在事务内的所有数据库操作,都是在事务连接建立之后进行的,所以会产生数据源没有切换的问题。
2、如何保证数据源切换正常,事务使用也正常?
想要使内部调用切换@DS起作用,就必须替换数据库连接,也就是改变事务的传播机制,使其产生新的事务,获取新的数据库连接。
可通过外部方法上方加 @Transactional注解,内部方法上方加@Transactional(propagation = Propagation.REQUIRES_NEW)注解进行解决。

三、解决方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
表示新建事务,如果当前存在事务,把当前事务挂起。
需要注意:添加了该注解的方法,需放在业务的最后方处理,确保挂起的事务方法均已执行成功,然后再去处理开启新事务的方法。
因为该内部事务方法异常时,会造成外部事务回滚。但是外部事务异常并不会回滚该内部事务方法。
就拿我们示例来说,在MethodA上方加 @Transactional(rollbackFor = Exception.class)注解,在内部调用操作tableB的方法operate2();上方加@Transactional(propagation = Propagation.REQUIRES_NEW)注解。
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableB")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5. // ........

  6.   @Override
  7.     @Transactional(propagation = Propagation.REQUIRES_NEW)
  8.     public Boolean operate2()(XXXX xxxx) {
  9.         return save(xxxx);
  10.     }
  11. }
复制代码
如果定义operate2()调用在前,operate1()调用在后。若operate1()方法异常,operate2()将不会回滚。
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableA")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5.    // ........

  6.     @Override
  7.     @Transactional(rollbackFor = Exception.class)
  8.     public R<?> MethodA(XXXX xxxx) {
  9.    
  10.       // 操作tableB的方法
  11.       operate2(xxxx);
  12.       // 操作tableA的方法
  13.       operate1(xxxx);
  14.     }
  15. }
复制代码
如果定义operate1()调用在前,operate2()调用在后。若有方法调用异常,则都会回滚。
  1. @Service
  2. @AllArgsConstructor
  3. @DS("tableA")
  4. public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

  5.    // ........

  6.     @Override
  7.     @Transactional(rollbackFor = Exception.class)
  8.     public R<?> MethodA(XXXX xxxx) {
  9.    
  10.       // 操作tableA的方法
  11.       operate1(xxxx);
  12.       // 操作tableB的方法
  13.       operate2(xxxx);
  14.     }
  15. }
复制代码
因此需注意,配置了@Transactional(propagation = Propagation.REQUIRES_NEW)注解的方法调用,应放在业务最后方处理。
回复

使用道具 举报

3

主题

6

帖子

19

积分

个人用户

积分
19
发表于 2022-6-28 15:15:44 | 显示全部楼层
原来是这样,学到了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表