Spring 事务的传播行为(Propagation)有哪些?
在 Spring 框架中,事务的传播行为(Propagation Behavior)是指:当一个带有事务的方法被另一个方法调用时,Spring 应该如何处理这个事务?(例如:是加入现有事务,还是创建一个新事务,或者是抛出异常?)
Spring 在 Propagation 枚举类中定义了 7 种 事务传播行为。为了方便理解,我们可以将它们分为三大类:
第一类:支持当前事务(最常用)
这类传播行为的核心是:如果当前已经有事务了,就加入进去。
1. REQUIRED(Spring 默认)
- 规则:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
- 特点:内外方法共享同一个物理事务。如果内部方法抛出异常发生回滚,外部方法也会跟着回滚。
- 适用场景:绝大多数的业务场景(增删改),Spring 默认就是这个。
2. SUPPORTS
- 规则:如果当前存在事务,则加入该事务;如果当前没有事务,就以非事务的方式继续运行。
- 适用场景:通常用于查询操作。如果有其他事务带着它,它就跟着一起;如果没有,它自己也不强求。
3. MANDATORY
- 规则:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 适用场景:该方法必须在事务上下文中运行,通常用于某些绝对不能单独执行的核心底层方法。
第二类:不支持当前事务
这类传播行为的核心是:我不想和外部方法共用一个事务。
4. REQUIRES_NEW(非常常用,面试常考)
- 规则:无论当前是否存在事务,都会创建一个全新的事务。 如果当前存在事务,会将当前事务挂起(Suspend)。
- 特点:内部事务和外部事务是完全独立的。内部事务的提交或回滚,不会影响外部事务;外部事务的回滚,也不会影响内部事务。
- 适用场景:记录操作日志。无论核心业务是成功还是失败(回滚),日志都必须被记录下来(提交)。
5. NOT_SUPPORTED
- 规则:以非事务方式运行。如果当前存在事务,会将当前事务挂起。
- 适用场景:执行某些不需要事务的耗时操作(如发送邮件、调用外部远程 API),挂起事务可以避免长时间占用数据库连接和锁。
6. NEVER
- 规则:以非事务方式运行。如果当前存在事务,则抛出异常。
- 适用场景:绝对不能在事务中运行的方法,起到严格校验的作用。
第三类:嵌套事务
7. NESTED
- 规则:如果当前存在事务,则在嵌套事务(Nested Transaction)内执行;如果当前没有事务,则表现得和
REQUIRED一样(创建一个新事务)。 - 特点:依赖于 JDBC 的
Savepoint(保存点)机制。- 嵌套事务是外部事务的子事务。
- 如果内部(子)事务回滚,它只会回滚到自己执行前的保存点,不会导致外部事务回滚(前提是外部事务捕获了内部抛出的异常)。
- 如果外部(父)事务回滚,则内部(子)事务一定会跟着回滚。
- 子事务只有在父事务提交时,才会一起提交。
- 适用场景:复杂的批处理任务,其中某个子任务失败了允许跳过并记录,但不希望影响整个批处理任务的整体提交。
💡 核心对比与总结表
| 传播行为 | 如果有事务 | 如果没有事务 |
|---|---|---|
REQUIRED (默认) |
加入当前事务 | 新建事务 |
SUPPORTS |
加入当前事务 | 非事务执行 |
MANDATORY |
加入当前事务 | 抛出异常 |
REQUIRES_NEW |
挂起当前,新建事务 | 新建事务 |
NOT_SUPPORTED |
挂起当前,非事务执行 | 非事务执行 |
NEVER |
抛出异常 | 非事务执行 |
NESTED |
创建保存点(嵌套执行) | 新建事务 |
⚠️ 经典面试题 / 避坑指南
REQUIRES_NEW和NESTED的区别是什么?- 独立性:
REQUIRES_NEW是完全独立的两个事务,父事务回滚不影响子事务。NESTED是父子关系,父事务回滚,子事务一定回滚。 - 提交时机:
REQUIRES_NEW的子事务执行完立即提交。NESTED的子事务执行完不提交,必须等父事务结束才一起提交。
- 独立性:
- 同类中方法调用,事务传播不生效的问题(AOP 失效)
- 现象:在同一个类中,方法 A 没有
@Transactional,方法 B 有@Transactional(propagation = Propagation.REQUIRES_NEW)。A 调用 B 时,B 的事务不会生效。 - 原因:Spring 事务是基于 AOP 动态代理实现的。同类内部的方法直接调用(
this.B()),绕过了代理对象,直接调用了目标类的原始方法,因此事务切面不生效。 - 解决办法:
- 将方法 B 抽离到另一个 Service 类中被调用(推荐)。
- 在当前类中自己注入自己(Spring 循环依赖支持)。
- 使用
AopContext.currentProxy()获取代理对象后再调用。
- 现象:在同一个类中,方法 A 没有