Java-SpringBoot-@Transactional-事务注解的失效和不回滚的场景
前言
事务(Transaction)指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 SpringBoot 通过 @Transactional 注解大大简化了开发时事务使用的复杂度,但同时也引入了不少隐藏的坑。 使用不当会引起事务失效、不能回滚等情况,从而破坏记录的完整性。 我们梳理下导致事务失效的常见错误避免踩坑。
环境
Spring Boot (v2.6.7) ORM JPA 数据库驱动 rg.postgresql.Driver
事务不回滚
1. 类内调用
外部类调用没有加 @Transactional 注解的方法。
public void fx1_test(RuntimeException ex) {
fx1_test_2(ex);
}
@Transactional
public void fx1_test_2(RuntimeException ex) {
aService.save();
bService.save();
if(ex!=null)
throw ex;
}
外部类通过调用fx1_test(),间接调用了使用事务的fx1_test_2()方法,发生异常时事务不会回滚。
2. 多线程调用
如果业务和事务入口不在同一个线程里,事务也是不生效的。
3. 错误的传播特性
- Propagation.NOT_SUPPORTED
声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,当前事务会被挂起,调用结束后,原先的事务会恢复执行。 - Propagation.REQUIRESNEW
不管是否存在事务,该方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。 - Propagation.NEVER
该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
4. 自己吞了异常
使用try…catch 语句块将异常吞掉,没有向外层抛出,事务不回滚。
5. 自定义了回滚异常
‘’’ @Transactional(noRollbackFor = MyException.class) 设置了noRollbackFor属性值,并抛出了该异常时,事务不回滚。 ‘’’
事务失效
1. 方法非public修饰,访问权限问题
private方法,protected和默认可见性方法不能添加事务,加了不生效;
2. 方法用final修饰
private方法,final方法和static方法不能添加事务,加了也不生效;
3. 未被spring管理
基于注解的事务是通过 AOP 方式织入的,未被Spring管理的Bean必然不能使用事务;
4. 手动抛了别的异常
事务的四大特性(ACID)
- 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 - 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(数据不被破坏 - 隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰. - 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的.即使系统重启也不会丢失.
事务的七种传播模式
- 1、REQUIRED(默认模式):业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
- 2、NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,当前事务会被挂起,调用结束后,原先的事务会恢复执行。
- 3、REQUIRESNEW:不管是否存在事务,该方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
- 4、 MANDATORY:该方法只能在一个已经存在的事务中执行,此方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
- 5、SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
- 6、NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
- 7、NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
总结
避免事务失效,还是应该有好的编程习惯:
- 项目结构层次清晰(controller、service、repository);
- 数据层保证单一职责(一个类操作一个数据表);
- 操作多表的方法,封装在 service 层;
- 事务注解 @Transaction 加在 serivce 层的pulbic方法上;
- controller 直接调用 service 层加注解的方法;
|