Spring事务开发经验 回滚和不回滚?
关于Spring事务原理的知识可以看我的一篇文章:Spring事务原理 如何增强、回滚、提交。那么在明白事务原理之后,工作中使用Spring事务控制时候需要注意什么?我根据工作经验列出几个注意点:
- 开启事务需要经过代理对象调用
- 避免长事务,容易导致死锁和锁超时
- 嵌套事务间发生异常是回滚还是不回滚?
本文针对第3个问题配合源码和实验例子说明,什么情况下抛出异常事务回滚。Spring 事务中 catch 异常后会回滚吗?有2种说法:
- 如果事务中 catch 了异常并且不抛出,事务不会回滚。
- 如果事务中 catch 了异常,但是事务依然回滚。
以下实验均是基于 read commited隔离级别,SpringBoot 2.5.6版本。
单个service,单个事务方法内catch异常
实验结论:在catch完后不再重新抛出情况下,事务不受影响,成功提交
双 service 配合,双方法同事务,catch异常
在抛出异常的方法体外catch,让Spring框架感知到异常
A函数 UserService,开启一个事务
B函数 ComodityService,融入当前事务中
实验结论:整体事务失败回滚,出现 Transaction silently rolled back because it has been marked as rollback-only
在抛出异常的方法体内catch异常,让Spring框架感知不到异常
实验结论:整体事务执行成功,包括insertTest() insertB(),2条数据都插入成功。
双 Service 双函数、双事务,catch异常
这个实验和上面实验最核心区别是开启了双事务,一个大事务和一个小事务,两个事务是独立的
A函数,UserService插入user。B函数插入goods,CommodityService 开启新事务,此时是通过代理类调用的所以新事务生效。
insertGoods方法开启新事务,虽然也是在方法内抛出异常让Spring框架感知到了,但是并不影响外层insertUser事务。
实验结论:A函数事务执行成功,B函数的事务执行失败回滚。
怎么解释这个现象?看日志,B函数开启了一个新事务,挂起了A事务。
B事务执行失败回滚后,Spring恢复了挂起的A事务继续执行,最后成功提交。由此可以推理出,Transaction silently rolled back because it has been marked as rollback-only
只会出现在多个代理对象的同个事务内函数嵌套执行时的情况,当某个方法抛出异常,由于是在同个事务内就算你catch了异常,偷偷消化了。但是被Spring感知到了,根据原子性原则,由于同个事务所有操作必须一起成功或失败,所以Spring选择回滚整体事务!
如果是同个代理对象内的多个函数嵌套执行,虽然也在同个事务内但后续的调用链都是基于目标对象this方法内调用没有走代理,所以异常是不会被Spring感知到的(除非第一个开启事务的函数抛出异常被Spring感知到),所以事务不会回滚。