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

举报
三婶儿 发表于 2023/04/29 18:01:34 2023/04/29
【摘要】 MyBatis-plus配置了多数据源,添加事务后,数据源切换失败了...

image.png

说明:MyBatis-plus配置了多数据源,添加事务后,数据源切换失败了…

一、场景描述

项目当中使用的多数据源,Impl中有个方法:MethodA。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {

    @Override
    public R<?> MethodA(XXXX xxxx) {
      
    }
}

该方法中同时操作了两张表:tableA、tableB(tableA、tableB来自两个数据源)。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
  // ........
  
    @Override
    public R<?> MethodA(XXXX xxxx) {
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

出于数据一致性考虑,楼主在MethodA上方加了 @Transactional(rollbackFor = Exception.class)

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

运行项目后发现,执行操作tableB的数据时,数据源并没有切换。

image.png

然而,去掉该事务注解后,数据源则切换正常。但不能保证数据一致性。这…

二、相关疑问

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)注解。

@Service
@AllArgsConstructor
@DS("tableB")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
 // ........

  @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Boolean operate2()(XXXX xxxx) {
        return save(xxxx);
    }
}

如果定义operate2()调用在前,operate1()调用在后。若operate1()方法异常,operate2()将不会回滚。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
    
      // 操作tableB的方法
      operate2(xxxx);
      // 操作tableA的方法
      operate1(xxxx);
    }
}

如果定义operate1()调用在前,operate2()调用在后。若有方法调用异常,则都会回滚。

@Service
@AllArgsConstructor
@DS("tableA")
public class XXXXServiceImpl extends ServiceImpl<XXXXMapper, XXXX> implements XXXXService {
 
   // ........
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> MethodA(XXXX xxxx) {
    
      // 操作tableA的方法
      operate1(xxxx);
      // 操作tableB的方法
      operate2(xxxx);
    }
}

因此需注意,配置了@Transactional(propagation = Propagation.REQUIRES_NEW)注解的方法调用,应放在业务最后方处理。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。