← 返回题目列表

Spring 事务的七种传播行为有什么区别?

高频 困难 第 16 / 30 题 更新于 2026/07/26
Spring 事务传播行为REQUIRES_NEWNESTED

简化版

传播行为决定“事务方法被另一个事务方法调用时,加入、创建、挂起还是拒绝事务”。最常用的是 REQUIRED:有事务就加入、没有就新建;REQUIRES_NEW 总是使用独立事务,NESTED 则通常在同一物理事务内使用保存点实现局部回滚。

详细版

  • REQUIRED:加入现有事务,否则创建事务,默认值。
  • REQUIRES_NEW:挂起现有事务,创建独立物理事务。
  • NESTED:有事务时创建保存点,没有时按 REQUIRED 执行。
  • SUPPORTS:有事务就加入,没有则以非事务方式执行。
  • NOT_SUPPORTED:挂起现有事务,以非事务方式执行。
  • MANDATORY:必须已有事务,否则抛异常。
  • NEVER:必须没有事务,已有事务则抛异常。

传播设置只有在调用真正经过 Spring 事务代理时才生效。同类 this 调用绕过代理,不会创建一个新的传播边界。

完整版教学

一、逻辑事务与物理事务

每个带 @Transactional 的方法可以视为一个逻辑事务边界,但多个 REQUIRED 方法可能共享同一个数据库连接和物理事务。内层逻辑事务若把共享事务标记为 rollback-only,外层即使捕获异常也不能把这个物理事务重新变成可提交状态。

@Transactional
public void createOrder() {
    inventoryService.deduct(); // REQUIRED,加入当前事务
}

两次方法调用有两个逻辑作用域,却通常只有一次最终提交或回滚。

二、REQUIRED 与 UnexpectedRollbackException

如果内层 REQUIRED 方法发生运行时异常并将事务标记为 rollback-only,外层捕获异常后继续正常返回,事务管理器在外层尝试提交时会发现只能回滚,并抛出 UnexpectedRollbackException。这样可以防止调用方误以为提交成功。

不要用捕获异常后继续执行的方式强行挽救已经标记回滚的事务。应明确失败是否需要整体回滚,或把确实需要独立提交的操作放入合适的新事务边界。

三、REQUIRES_NEW 的独立性与资源代价

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(AuditLog log) {
    repository.save(log);
}

REQUIRES_NEW 挂起外层事务,内层获得独立事务,可以先于外层提交;外层之后回滚,不会撤销已经提交的内层日志。反过来,内层回滚也不会自动把外层标成回滚。

独立事务通常需要另一条数据库连接。如果许多线程持有外层连接,同时等待内层从小型连接池取连接,可能耗尽连接池甚至互相等待。它不是“更安全”的 REQUIRED,必须为独立提交语义和连接资源付出代价。

四、NESTED 与保存点

NESTED 通常在现有物理事务中创建 JDBC savepoint。内层失败可以回滚到保存点,外层仍有机会继续;但外层最终回滚时,内层的修改也会一起回滚,因此它并不是独立事务。

保存点能力取决于事务管理器和底层资源,典型支持场景是 JDBC DataSourceTransactionManager。JPA 场景不能想当然地认为所有 NESTED 行为都受支持,应查看实际 PlatformTransactionManager 的能力和配置。

五、其余四种如何理解

SUPPORTS 表达“事务可有可无”,同一方法在不同调用链中可能获得不同一致性语义;NOT_SUPPORTED 明确要求非事务执行,并在存在事务时挂起它。MANDATORY 用于强制调用方先建立事务,NEVER 则用于禁止在事务内调用。

所谓“挂起”由事务管理器协调,具体资源是否真正支持挂起取决于实现。传播行为是 Spring 对事务上下文的抽象,不应脱离所使用的事务管理器讨论所有细节。

六、传播行为怎么选

核心业务通常从 REQUIRED 开始。只有审计日志、消息落库等确实要求与外层成败解耦的操作,才考虑 REQUIRES_NEW;需要同一事务中局部回退且底层支持保存点时,才考虑 NESTED。

选择前先画清楚提交边界、失败传播和资源占用,再写注解。传播级别不能弥补错误的业务一致性设计,也不能替代跨服务场景下的消息、补偿或分布式事务方案。

七、把七种行为放进同一张决策表

设外层 outer() 已有事务,内层 inner() 再声明传播行为。七种行为的差别可以统一看成“使用外层、创建新事务、非事务执行或直接拒绝”四类,而不是背七段孤立定义。

传播行为外层已有事务外层无事务与外层提交关系
REQUIRED加入新建共享物理事务
REQUIRES_NEW挂起外层并新建新建独立提交或回滚
NESTED通常建保存点按 REQUIRED 新建同一物理事务,外层回滚仍全回滚
SUPPORTS加入非事务执行随调用环境变化
NOT_SUPPORTED挂起后非事务执行非事务执行不参与外层事务
MANDATORY加入抛异常强制调用方已有事务
NEVER抛异常非事务执行强制当前不能有事务

连接池算例能说明 REQUIRES_NEW 的代价:若有 20 个并发线程,每个线程都持有 1 条外层连接,并同时进入需要另一条连接的 REQUIRES_NEW,而连接池上限也是 20,所有线程都可能等待无法释放的第 21 条连接。官方建议连接池容量应明显大于并发线程数,但实际设计还要限制嵌套新事务与峰值并发,而不是只盲目扩池。

REQUIRED:     outer connection ───── inner uses same connection ───── commit once
REQUIRES_NEW: outer connection ─suspend─┐
                                        └─ second connection ─ commit
NESTED:       outer connection ─ savepoint ─ rollback to savepoint ─ final commit

记忆钩子:REQUIRED 共享“事务身份”,REQUIRES_NEW 更换“物理事务”,NESTED 只在同一物理事务里增加“回退刻度”。

八、常见误区与追问

  • 误区:REQUIRES_NEW 是比 REQUIRED 更安全的写法。 它改变提交语义并占用额外资源,只适合确实要求独立成败的操作。
  • 误区:NESTED 就是轻量版 REQUIRES_NEW。 NESTED 通常使用同一物理事务的保存点,外层最终回滚时内层修改也无法保留。
  • 误区:外层捕获内层 REQUIRED 异常后一定能正常提交。 内层可能已把共享事务标记 rollback-only,外层提交时会得到 UnexpectedRollbackException。
  • 追问:为什么传播行为有时完全不生效? 同类 this 调用绕过事务代理时,内层注解不会形成新的逻辑事务边界。
  • 追问:NESTED 是否在所有事务管理器上都可用? 否,它依赖保存点及事务管理器支持,JDBC 常见,JPA 等场景必须核对实际 manager 能力。
  • 追问:审计日志应该一律使用 REQUIRES_NEW 吗? 只有日志确实必须独立提交且能接受连接占用、外层未提交数据不可见等语义时才使用,也可评估事务后事件或可靠消息。
  • 追问:NOT_SUPPORTED 的“挂起”一定由数据库原生实现吗? 传播是 Spring 的抽象,具体挂起方式取决于事务管理器,不能脱离实现承诺统一底层行为。

九、加强记忆

REQUIRED 是同船共进退,REQUIRES_NEW 是暂停外船、另开一条独立船,NESTED 是同一条船上的保存点。其余四种负责允许、禁止或强制事务上下文;所有传播规则都以调用经过事务代理且底层事务管理器支持为前提。