【Spring事务详解】--- 3.事务失效的八种场景
文章目录
- 前言
- 事务失效的八种场景
-
- 1.异常未抛出
- 2.异常与rollback不匹配
- 3.方法内部直接调用
- 4.在另一个线程中使用事务
- 5.注解作用到private级别的方法上
- 6.final类型的方法
- 7.数据库存储引擎不支持事务
- 8.事务的传播类型
前言
Spring事务详解连载
【Spring事务详解】— 1.事务传播的案例演示
【Spring事务详解】— 2.事务应用的注意事项
【Spring事务详解】— 3.事务失效的八种场景
【Spring事务详解】— 4.事务管理器的架构分析
【Spring事务详解】— 5.事务管理器TransactionSynchronizationManager分析
【Spring事务详解】— 6.事务创建的流程分析
【Spring事务详解】— 7.事务提交、回滚的流程分析
【Spring事务详解】— 8.beforeCommit、beforeCompletion、afterCommit、afterCompletion实现分析
这篇文章主要针对事务失效的情况来分析,应该也是最常遇到的问题。
事务失效的八种场景
1.异常未抛出
被捕获的异常一定要抛出,否则是不会回滚的。
// t1Service@Transactionalpublic void func() { try { testMapper.updateT1(); t2Service.func(); int i = 1 / 0; } catch (Exception e) { // 异常捕获了,未抛出,导致异常事务不会回滚。 e.printStackTrace(); }}// t2Service@Transactionalpublic void func() { testMapper.updateT2();}
2.异常与rollback不匹配
@Transactional
默认情况下,只会回滚RuntimeException
和Error
及其子类的异常,如果是受检异常或者其他业务类异常是不会回滚事务的。
@Transactionalpublic void func() throws Exception { try { testMapper.updateT1(); t2Service.func(); int i = 1 / 0; } catch (Exception e) { // 默认情况下非运行时异常不会回滚 throw new Exception(); }}
修改方式也很简单,@Transactional
支持通过rollbackFor
指定回滚异常类型
// 改成rollbackFor = Exception.class即可@Transactional(rollbackFor = Exception.class)public void func() throws Exception { try { testMapper.updateT1(); t2Service.func(); int i = 1 / 0; } catch (Exception e) { throw new Exception(); }}
3.方法内部直接调用
func2
方法是由func
调用,虽然func2
方法上加了@Transactional
注解,但事务不会生效,testMapper.updateT2()
执行的方法并不会回滚
public void func() { testMapper.updateT1(); func2();}@Transactionalpublic void func2() { testMapper.updateT2(); int i = 1 / 0;}
修改方式也很简单,通过注入的方式调用即可
@Servicepublic class T1Service { @Resource private TestMapper testMapper; // 注入T1Service对象 @Resource private T1Service t1Service; public void func() { testMapper.updateT1(); // 通过注入的方式调用自身的方法 t1Service.func2(); } @Transactional public void func2() { testMapper.updateT2(); int i = 1 / 0; }}
小插曲,SpringBoot 2.6.0版本开发,默认禁止循环依赖,所以如果你使用的版本是2.6.0之后的,那么启动会遇到如下报错
As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
修改方式:在配置文件中把允许循环依赖打开即可。
spring.main.allow-circular-references=true
当然,你也可以直接使用AopContext
的方式
public void func() { testMapper.updateT1(); T1Service t1Service = (T1Service) AopContext.currentProxy(); t1Service.func2();}@Transactionalpublic void func2() { testMapper.updateT2(); int i = 1 / 0;}
4.在另一个线程中使用事务
Spring事务管理的方式就是通过ThreadLocal把数据库连接与当前线程绑定,如果新开启一个线程自然就不是一个数据库连接了,自然也就不是一个事务。
t2Service.func()
方法操作的数据并不会被回滚
@Transactionalpublic void func() { testMapper.updateT1(); new Thread(() -> t2Service.func()).start(); int i = 1 / 0;}
5.注解作用到private级别的方法上
当你写成如下这样时,IDEA
直接会给出提示Methods annotated with ‘@Transactional’ must be overridable
原因很简单,private
修饰的方式,spring
无法为其生成代理。
public void func() { t1Service.func2();}@Transactionalprivate void func2() { testMapper.updateT1(); int i = 1 / 0;}
6.final类型的方法
这个与private
道理是一样的,都是影响了Spring
生成代理对象,同样IDEA
也会有相关提示。
7.数据库存储引擎不支持事务
注意,如果你使用的是MySQL数据库,那么常用的存储引擎中只有InnoDB才支持事务,像MyISAM是不支持事务的,其他存储引擎都是针对特定场景下使用的,一般也不会用到,不做讨论。
8.事务的传播类型
前面的文章中已经对事务的传播类型做过介绍了,有的传播类型会以非事务方式执行,有的传播则会新开启一个事务,这些都需要额外注意。
REQUIRED:支持当前事务,如果当前不存在则新开启一个事务(默认配置)SUPPORTS:支持当前事务,如果当前不存在事务则以非事务方式执行MANDATORY:支持当前事务,如果当前不存在事务则抛出异常REQUIRES_NEW:创建一个新事务,如果当前已存在事务则挂起当前事务NOT_SUPPORTED:以非事务方式执行,如果当前已存在事务则挂起当前事务NEVER:以非事务方式执行,如果当前已存在事务则抛出异常NESTED:如果当前存在事务,则在嵌套事务中执行,否则开启一个新事务