Spring 事务传播机制 ⭐⭐⭐⭐⭐
面试频率:★★★★★
工作频率:★★★★★
⚡30 秒速记
🟢 什么是事务传播?
一个事务方法调用另一个事务方法时,决定被调用方法应该加入当前事务,还是创建新事务。
最重要的两个:
REQUIRED
外面有事务 → 加入
外面没事务 → 创建
默认传播行为
REQUIRES_NEW
不管外面有没有事务 → 都创建新事务
外面有事务 → 先挂起外层事务
一、为什么需要事务传播?
例如:
@Transactional
public void createOrder() {
saveOrder();
}
saveOrder() 也有事务:
@Transactional
public void saveOrder() {
}
问题:
createOrder已经有事务
↓
调用saveOrder
↓
saveOrder怎么办?
它可以:
加入createOrder的事务
也可以:
自己重新创建一个事务
Spring 用:
Propagation
决定这个行为。
这就是:
事务传播机制。
二、REQUIRED ⭐⭐⭐⭐⭐
REQUIRED 是:
Spring 默认的事务传播行为。
所以:
@Transactional
默认相当于:
@Transactional(
propagation = Propagation.REQUIRED
)
REQUIRED 规则
外面有事务
↓
加入外面的事务
外面没有事务
↓
创建一个新事务
三、REQUIRED 举例
例如:
@Transactional
public void createOrder() {
saveOrder();
}
另一个 Bean 中:
@Transactional
public void saveOrder() {
}
假设调用经过 Spring 事务代理。
执行:
调用createOrder
↓
开启事务A
↓
调用saveOrder
↓
发现已经存在事务A
↓
加入事务A
最终:
createOrder
+
saveOrder
↓
事务A
也就是说:
两个方法属于同一个事务。
如果:
saveOrder发生异常
导致事务回滚:
事务A整体回滚
所以:
createOrder的数据库操作
+
saveOrder的数据库操作
↓
一起回滚
四、REQUIRES_NEW ⭐⭐⭐⭐⭐
使用:
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
意思:
每次都需要一个新的事务。
如果外层已经存在事务:
挂起外层事务
↓
创建新事务
例如:
createOrder
↓
事务A
调用:
saveLog
而 saveLog:
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void saveLog() {
}
执行流程:
事务A正在执行
↓
调用saveLog
↓
发现REQUIRES_NEW
↓
挂起事务A
↓
创建事务B
↓
执行saveLog
↓
提交事务B
↓
事务B结束
↓
恢复事务A
↓
继续执行createOrder
所以:
createOrder → 事务A
saveLog → 事务B
两个事务:
相互独立。
五、REQUIRED 和 REQUIRES_NEW 区别 ⭐⭐⭐⭐⭐
REQUIRED
事务A
↓
调用B
↓
B加入事务A
最终:
A + B
↓
同一个事务
REQUIRES_NEW
事务A
↓
调用B
↓
挂起事务A
↓
创建事务B
↓
执行B
↓
提交事务B
↓
恢复事务A
最终:
A → 事务A
B → 事务B
一张图记住
REQUIRED:
A事务
↓
B
↓
加入A事务
A + B = 一个事务
REQUIRES_NEW:
A事务
↓
挂起A
↓
创建B事务
↓
B事务提交
↓
恢复A
A、B = 两个事务
六、REQUIRES_NEW 有什么用?
一个非常典型的场景:
保存操作日志 / 失败记录。
例如:
@Transactional
public void createOrder() {
saveOrder();
logService.saveLog();
throw new RuntimeException();
}
需求:
订单失败
↓
订单数据回滚
但是:
操作日志必须保留
七、如果 saveLog 使用 REQUIRED
例如:
@Transactional
public void saveLog() {
}
因为默认:
REQUIRED
所以:
createOrder
↓
事务A
↓
saveOrder
↓
saveLog
↓
也加入事务A
最后:
throw new RuntimeException();
事务A:
回滚
结果:
订单 ❌ 回滚
日志 ❌ 也回滚
问题:
本来就是为了记录失败日志,结果日志自己也没了。
八、使用 REQUIRES_NEW
修改:
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void saveLog() {
}
执行:
createOrder
↓
事务A
↓
保存订单
↓
调用saveLog
↓
挂起事务A
↓
创建事务B
↓
保存日志
↓
提交事务B
↓
恢复事务A
↓
createOrder发生异常
↓
事务A回滚
最终:
订单:
回滚 ❌
日志:
已经提交 ✅
因为:
订单 → 事务A
日志 → 事务B
是两个独立事务。
九、REQUIRES_NEW 最大的坑:自调用 ⭐⭐⭐⭐⭐
例如:
@Service
public class OrderService {
@Transactional
public void createOrder() {
saveLog();
}
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void saveLog() {
}
}
你可能认为:
createOrder
↓
事务A
↓
saveLog
↓
REQUIRES_NEW
↓
事务B
但是:
可能不会按照预期创建事务B。
十、为什么 REQUIRES_NEW 可能失效?
因为:
saveLog();
实际上相当于:
this.saveLog();
这是:
同一个对象内部调用。
你前面学过:
@Transactional
↓
依赖Spring AOP代理
正常调用:
外部调用
↓
Spring代理对象
↓
发现@Transactional
↓
事务拦截器
↓
创建事务
↓
执行目标方法
但是自调用:
OrderService代理
↓
createOrder()
↓
原始OrderService
↓
this.saveLog()
saveLog():
没有重新经过:
Spring代理对象
所以:
Spring 没机会读取:
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
也就不会按预期:
挂起事务A
↓
创建事务B
十一、实际项目怎么解决?
最简单常见的方式:
拆成另外一个 Spring Bean。
例如:
@Service
public class LogService {
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
public void saveLog() {
// 保存日志
}
}
OrderService:
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional
public void createOrder() {
saveOrder();
logService.saveLog();
}
}
这时候:
调用OrderService代理
↓
createOrder
↓
开启事务A
↓
调用LogService
↓
经过LogService代理
↓
发现REQUIRES_NEW
↓
挂起事务A
↓
创建事务B
↓
执行saveLog
↓
提交事务B
↓
恢复事务A
这样:
REQUIRES_NEW 才能正常通过代理发挥作用。
十二、和 Spring AOP 串起来 ⭐⭐⭐⭐⭐
现在你前面学的:
Spring AOP
和事务传播可以串起来:
@Transactional
↓
Spring发现事务注解
↓
创建AOP代理
↓
调用代理对象
↓
事务拦截器
↓
读取Propagation
↓
决定事务怎么传播
如果:
REQUIRED
那么:
有事务 → 加入
如果:
REQUIRES_NEW
那么:
有事务 → 挂起
↓
创建新事务
十三、Spring 一共有 7 种事务传播行为
REQUIRED
REQUIRES_NEW
SUPPORTS
MANDATORY
NOT_SUPPORTED
NEVER
NESTED
面试优先掌握:
REQUIRED ⭐⭐⭐⭐⭐
REQUIRES_NEW ⭐⭐⭐⭐⭐
其他几个:
知道即可。
十四、SUPPORTS
意思:
外面有事务
↓
加入事务
外面没事务
↓
不用事务
简单理解:
有就用,没有也无所谓。
十五、MANDATORY
意思:
必须存在外层事务
如果:
外面没有事务
直接:
抛异常
十六、NOT_SUPPORTED
意思:
不使用事务。
如果外面有事务:
挂起外层事务
↓
自己不用事务执行
↓
执行完成
↓
恢复外层事务
十七、NEVER
意思:
禁止存在事务。
如果:
外面有事务
直接:
抛异常
十八、NESTED
意思:
嵌套事务。
可以粗略理解:
外层事务A
↓
创建保存点 Savepoint
↓
执行内部事务B
B出现异常时:
可以:
回滚到保存点
而不一定直接结束整个外层事务。
注意:
NESTED
≠
REQUIRES_NEW
十九、NESTED 和 REQUIRES_NEW 区别
REQUIRES_NEW
事务A
↓
挂起A
↓
创建独立事务B
A、B:
两个独立事务
NESTED
事务A
↓
Savepoint
↓
事务B
B:
属于外层事务中的:
嵌套执行
不是完全独立的新事务。
二十、7种传播行为速记
| 传播行为 | 外面有事务 | 外面没事务 |
|---|---|---|
| REQUIRED | 加入 | 创建 |
| REQUIRES_NEW | 挂起外层,创建新的 | 创建 |
| SUPPORTS | 加入 | 不使用事务 |
| MANDATORY | 加入 | 报错 |
| NOT_SUPPORTED | 挂起 | 不使用事务 |
| NEVER | 报错 | 不使用事务 |
| NESTED | 嵌套执行 | 创建 |
最重要:
REQUIRED
有就加入
没就创建
REQUIRES_NEW
永远要新事务
💼 工作场景
场景1:普通业务
例如:
创建订单
↓
保存订单明细
↓
修改订单状态
希望:
全部成功
或者
全部失败
通常:
REQUIRED
大家加入同一个事务。
场景2:独立日志
例如:
订单操作失败
但是:
失败日志必须保存
可以考虑:
REQUIRES_NEW
让日志使用:
独立事务
🎤 面试回答(30秒)
Spring 事务传播机制用于解决一个事务方法调用另一个事务方法时,被调用方法应该如何处理事务的问题。
默认传播行为是 REQUIRED,如果当前存在事务就加入当前事务,如果不存在就创建新事务。
REQUIRES_NEW 则无论当前是否存在事务都会使用一个新的事务,如果外层已经有事务,会先挂起外层事务,内部新事务执行完成后再恢复外层事务。
另外事务传播依赖 Spring AOP 代理,所以同类内部自调用可能导致传播行为无法按预期生效。
🎯 面试追问
Q1:什么是事务传播?
答:
一个事务方法调用另一个事务方法时,决定内部方法如何处理事务。
Q2:@Transactional 默认传播行为是什么?
答:
Propagation.REQUIRED
Q3:REQUIRED 是什么意思?
答:
有事务 → 加入
没事务 → 创建
Q4:REQUIRES_NEW 是什么意思?
答:
有事务 → 挂起外层事务 → 创建新事务
没事务 → 创建新事务
Q5:REQUIRED 和 REQUIRES_NEW 最大区别?
答:
REQUIRED:
和外层共用一个事务
REQUIRES_NEW:
创建独立的新事务
Q6:为什么保存日志可能使用 REQUIRES_NEW?
答:
为了让日志事务和主业务事务相互独立。
即使:
主业务回滚
日志已经提交:
仍然可以保留
Q7:为什么 REQUIRES_NEW 可能失效?
答:
如果:
this.saveLog();
同类内部调用:
没有经过 Spring AOP 代理。
所以:
事务拦截器无法处理:
Propagation.REQUIRES_NEW
Q8:怎么解决自调用问题?
答:
一种常见方式:
把需要独立事务的方法:
拆到另外一个Spring Bean
通过另一个 Bean 的代理对象调用。
Q9:REQUIRES_NEW 和 NESTED 一样吗?
答:
不一样。
REQUIRES_NEW
↓
独立新事务
NESTED
↓
基于外层事务进行嵌套执行
通常涉及Savepoint
⚠️ 工作踩坑
1. 同类调用 REQUIRES_NEW
例如:
this.saveLog();
可能绕过:
Spring代理
导致:
事务传播行为没有按预期生效。
2. 认为加了 @Transactional 就一定有事务
错误。
还要考虑:
是不是Spring Bean
↓
是不是通过代理调用
↓
有没有自调用
↓
异常是否符合回滚规则
3. REQUIRES_NEW 不是越多越好
REQUIRES_NEW:
会创建独立事务。
如果大量使用:
事务数量增加
数据库连接占用增加
业务逻辑复杂度增加
所以:
只在真正需要:
独立提交 / 独立回滚
的业务场景使用。
❓ 自测
-
什么是事务传播机制?
-
Spring 默认事务传播行为是什么?
-
REQUIRED 是什么意思?
-
REQUIRES_NEW 是什么意思?
-
REQUIRED 和 REQUIRES_NEW 最大区别是什么?
-
外层存在事务时,REQUIRES_NEW 会怎么处理?
-
为什么操作日志可能使用 REQUIRES_NEW?
-
外层事务回滚,已经提交的 REQUIRES_NEW 内层事务会一起回滚吗?
-
为什么同类内部调用可能导致 REQUIRES_NEW 失效?
-
事务传播机制和 Spring AOP 有什么关系?
-
Spring 有哪 7 种事务传播行为?
-
SUPPORTS 是什么意思?
-
MANDATORY 是什么意思?
-
NOT_SUPPORTED 是什么意思?
-
NEVER 是什么意思?
-
NESTED 和 REQUIRES_NEW 有什么区别?