SpringBoot事务管理

场景:Excel导入人口信息。需要先删除重复的,然后循环插入一条条信息(due to 许多原因,整个流程最开始是放在一个方法里的)。由于导入的一条数据涉及多个表格的查询和插入,会存在需要回滚的情况。e.g. A数据先插入,B数据后插入。 由于B数据插入错误,A也必须回滚到插入前的状态。但一直没有成功。刚好对spring boot的事务回滚不是很了解,现在记录下学习的情况。

一 、事务的两种管理机制

a. 声明式事务

基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional 注解了。

b.编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。如下所示(手动回滚):

        try {
				//执行代码
            }
            return errorList;
        } catch (Exception e) {
        	  e.getMessage()
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

二、 回滚失败的几种情况

最开始我当时直接照葫芦画瓢加了个注释以为就能用了。后来慢慢倒查信息,学到了比较完整的事务管理,所以先记录这个东西。
由于我仅仅需要对单个插入动作进行回滚,并不是整个插入流程(从删除重复到全部插入完毕)。所以最开始我把插入循环提取出来,放在同一个类中并在方法上作了注释。引出了第一个回滚失败的情况。

1. 同一个类中方法调用,导致 @Transactional 失效

这个是我最开始犯的错误。为什么同一个类中的方法被调用 @Transactional就会失效?
这跟spring AOP(埋个点,日后学习下AOP)有关。由于使用spring aop代理,事务的有效范围仅在aop代理能找到的情况。但方法调用同一个类中的内部方法并不会经过aop代理,因此@Transactional失效。
如果整个方法确实需要放在一个类中,这该怎么办呢?只需要在开头声明一下就行了。

public class ImportInfo{
	//  自己声明自己,哈哈哈
	private ImportInfo importInfo;
}

自己声明了自己以后,就会生成对象在AOP中被调用,@Transactional就有用了。

2. 异常被你的 catch“吃了”导致 @Transactional 失效

在意识到第一个错误后,我把循环插入的方法提取出来,扔到了一个工具类中。但仍然不能回滚。这就引出了第二个原因。由于不可抗力的原因,我的循环体中,必须用try catch来捕获异常并返回一个列表。而@Transactional 的触发也是需要有异常报出。这看起来并不冲突,却是“无解”的事情。 @Transactional 是在方法报错后,对整个方法进行回滚。但try catch会把代码块的报错合理化。即,代码报错异常后被catch捕获然后返回对应的措施。在整个方法层面讲,就是没有报错。

那,怎么解决呢?
最开始我和同事打算直接在catch中扔出来一个异常(throw new Exception();)发现IDE直接报错。这是因为这是一个编译时就报错的异常,而不是运行时报错的异常。我的同事,是个狠人,直接int i = 1/0; 整个理论上是可以的,但跟 throw new RuntimeException()一样,属于走bug的方法且无法返回return。
正确的解决方案是,在catch中手动事务回滚

          catch (Exception e) {
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return  exampleList;
        }

3. @Transactional 注解属性 rollbackFor 设置错误

这个是我在测试@Transactional时注意到的一个方面。当你给@Transactional设定回滚错误类型时,需要注意到这点。你希望发生什么类型的异常时回滚。一般来说,不写就是默认Exception.class。这是最高类型的异常。

4. @Transactional 应用在非 public 修饰的方法上

5. @Transactional 注解属性设置错误

这个涉及事务的传播机制

6. 数据库引擎不支持事务

三、事务的传播机制

①、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。

②、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。

③、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。

④、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。

⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。

⑥、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。

⑦、PROPAGATION_NESTED :nested ,嵌套。A和B底层采用保存点机制,形成嵌套事务。
带你读懂Spring 事务——事务的传播机制
这篇文章很详细的讲解了这7种的传播机制以及他们面对回滚时的不同个反应。

四、一些后续(22年12月)

哈哈哈,我也没想到这个能后续记录起来。事情的起因是项目中要用到多数据源配置。在A方法中使用了数据源X,且A方法中调用了B方法,而B方法使用了数据源Y。项目里使用注解@DS来切换数据源。

最开始我也只是使用最简单的@Transactional(rollbackFor = Exception.class)来注解B方法,但自测的时候发现数据源就是切不过去。最后通过查找资料和排查,发现切换数据源和事务产生了冲突。目前,我使用了propagation = Propagation.REQUIRES_NEW这个方法解决了冲突问题,但是否会有事务上的bug出现还未可知,在进一步学习ing。。。。。。
学习资料:SpringBoot多数据源切换详解,以及开启事务后数据源切换失败处理

ps:后来同事也发现了这个问题,在问过我知道了原因以后,他选择把事务删了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值