原文来自于:zha-ge.cn/java/121
面试高频:Spring 事务传播行为的核心价值是什么?
面试的时候,有一道老八股我几乎每次都能碰上:
"你能说说 Spring 的事务传播行为有几种吗?各自的作用是什么?"
刚入行的时候我也背得滚瓜烂熟:REQUIRED
、REQUIRES_NEW
、NESTED
......七种!
但有一次项目上线翻车,让我彻底明白了一个道理:事务传播从来不只是"几种模式的名字",它是保证业务一致性的"行为哲学"。
那次"事务传染病"事故
故事要从一个"插日志"的小需求说起:
主业务下订单,订单成功后插一条日志,日志失败也不能影响主业务。
我心想:这不简单?加个 @Transactional
就完事:
typescript
@Service
public class LogService {
@Transactional
public void saveLog(String msg) {
if (msg.contains("fail")) throw new RuntimeException("日志写入失败");
}
}
然后主流程这样调用:
typescript
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 1. 创建订单
orderRepository.save(...);
// 2. 写日志
logService.saveLog("fail");
}
}
结果------日志报错,订单也回滚了!主流程和子流程"同生共死"了。
我这才真正意识到:事务传播行为,决定的是不同事务方法之间"生死是否绑在一起"。
什么是事务传播行为?
一句话解释:传播行为(Propagation)决定了一个方法在被调用时,事务该怎么处理:是加入现有事务?新开一个?还是干脆不参加?
Spring 提供了 7 种常见的传播行为,默认是 REQUIRED
:
ini
@Transactional(propagation = Propagation.REQUIRED)
如果你不写,默认就是它。
七大传播行为全解析
1. REQUIRED(默认)------有就用,没有就建
📌 行为:如果存在事务,就加入;如果不存在,就新建一个。
这也是最常见的情况,也是我上面"插日志"翻车的原因:子事务加入了主事务,失败就一起回滚。
适用场景:
大部分正常业务逻辑,不需要事务独立。
2. REQUIRES_NEW ------我就是要自己干!
📌 行为:不管外面有没有事务,我都新建一个事务,暂停外层事务。
这就是解决"插日志"问题的关键:
typescript
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String msg) { ... }
这下日志方法再报错,也不会影响外层事务。外层事务会被挂起,日志的事务单独提交/回滚。
适用场景:
- 日志、消息发送等不该影响主流程的操作
- 审计、风控等独立性强的子操作
⚠️ 注意:必须跨 Service 调用才能生效(自调用不经过代理,传播策略不起作用)。
3. NESTED ------子事务有"后悔药"
📌 行为:如果有事务,就在当前事务中创建一个"保存点";没有事务就新建一个。
NESTED 看起来和 REQUIRES_NEW 有点像,但不同的是,它不暂停外层事务,而是在内部开了个"子事务",可以单独回滚到保存点。
举个例子:
- 外层事务插入订单
- 内层事务插入优惠券记录失败
- 外层事务可以选择"只回滚子事务",订单不受影响
适用场景:
- 主事务和子事务逻辑相关,但希望子事务可"后悔"
- 常用于分阶段处理
⚠️ 前提:底层数据库和数据源必须支持 Savepoint。
4. SUPPORTS ------有就参与,没有就算了
📌 行为:有事务就加入,没有就以非事务方式运行。
适用场景:
- 逻辑对事务无强依赖,但希望尽量复用事务
- 多用于"可有可无"的读操作
5. MANDATORY ------必须要有事务,否则报错
📌 行为:当前必须有事务,否则直接抛异常。
适用场景:
- 某些核心逻辑必须在事务中运行
- 强制保证调用方已经开启事务
6. NOT_SUPPORTED ------我不想参加
📌 行为:如果存在事务,先挂起它,再以非事务方式执行。
适用场景:
- 查询逻辑或者第三方调用不希望被事务包裹
- 防止事务超时、锁竞争
7. NEVER ------我就不在事务里!
📌 行为:如果当前存在事务,直接抛异常。
适用场景:
- 明确要求非事务环境,比如某些外部 API 或异步任务
踩坑瞬间:自调用 = 传播失效
记得我刚学 REQUIRES_NEW
的时候,最常犯的一个错就是"自己调自己":
typescript
@Transactional
public void createOrder() {
saveLog(); // 调用本类内部方法
}
结果事务传播根本没生效,子事务照样和主事务绑一起。
为什么?因为 Spring 的事务是通过代理对象实现的,自调用不会经过代理,传播行为就形同虚设。
👉 正确写法:
typescript
@Autowired
private LogService logService;
@Transactional
public void createOrder() {
logService.saveLog(); // 必须通过代理对象调用
}
面试官杀手锏回答
问:事务传播行为的核心价值是什么?
建议这样回答👇:
-
传播行为的核心价值在于控制"事务边界" :决定当前方法在被事务方法调用时,是复用事务、创建新事务,还是不参与事务,从而实现复杂场景下的数据一致性和隔离性。
-
通过灵活选择传播策略,我们可以解决主子事务独立 、分阶段回滚 、防止事务传染等实际问题。
-
常用的有:
REQUIRED
:默认行为,加入或新建事务;REQUIRES_NEW
:新事务独立提交回滚;NESTED
:保存点回滚机制;- 其余策略用于控制事务的存在性和参与方式。
写在最后:传播,是"事务的语言"
很多人把事务传播当成"配置选项",但在我看来,它更像是事务的"语言":
它决定了事务之间是共进退 ,还是各自为战 ;是强绑定 ,还是弱关联。
如果说隔离级别解决的是"一个事务内部的数据安全",那传播行为解决的就是"多个事务之间的协作逻辑"。
一句话记住:传播行为的核心价值,是让事务不再只有"有或没有"的二元世界,而是能在复杂业务场景下,优雅地控制"我和你"的关系。