bigywater.tech
Spring · 30

事务传播行为:7 种逐一实测

REQUIRED、REQUIRES_NEW、NESTED 到底有什么区别?外层回滚了里层还在吗?里层抛异常被外层吞掉会怎样?外面没有事务时每种传播行为是什么表现?这篇用 7 种传播行为乘 4 种场景,在 Spring Framework 7.0.9 和 MySQL 26.7.0 上逐个跑出来。

3 个
外层吞掉里层异常时,提交阶段抛 UnexpectedRollbackException 的传播行为个数(REQUIRED、SUPPORTS、MANDATORY)
2 个
外层回滚后里层的行仍保留的传播行为个数(REQUIRES_NEW、NOT_SUPPORTED)
13 对 12
REQUIRES_NEW 内层连接 id 与外层连接 id(另开了连接)

外面有事务,都成功

实测 · 本机执行输出
  REQUIRED       外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=true,inner 连接 12(outer 连接 12)
  REQUIRES_NEW   外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=true,inner 连接 13(outer 连接 12)
  NESTED         外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=true,inner 连接 12(outer 连接 12)
  SUPPORTS       外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=true,inner 连接 12(outer 连接 12)
  NOT_SUPPORTED  外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=false,inner 连接 13(outer 连接 12)
  MANDATORY      外有事务、都成功 → 提交后的行 outer+inner        inner 事务活跃=true,inner 连接 12(outer 连接 12)
  NEVER          外有事务、都成功 → 提交后的行 ✗ IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

四种加入同一个连接,REQUIRES_NEW 与 NOT_SUPPORTED 用另一个连接,NEVER 抛异常。

外层回滚

实测 · 本机执行输出
  REQUIRED       外有事务、inner 成功后 outer 抛异常 → 剩下的行 (空)
  REQUIRES_NEW   外有事务、inner 成功后 outer 抛异常 → 剩下的行 inner
  NESTED         外有事务、inner 成功后 outer 抛异常 → 剩下的行 (空)
  SUPPORTS       外有事务、inner 成功后 outer 抛异常 → 剩下的行 (空)
  NOT_SUPPORTED  外有事务、inner 成功后 outer 抛异常 → 剩下的行 inner
  MANDATORY      外有事务、inner 成功后 outer 抛异常 → 剩下的行 (空)
  NEVER          外有事务、inner 成功后 outer 抛异常 → 剩下的行 ✗ IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

加入外层事务的跟着回滚,独立事务或无事务的内层行留下。

里层失败外层捕获

实测 · 本机执行输出
  REQUIRED       外有事务、inner 抛异常被 outer 捕获 → outer 结束时 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only;行 (空)
  REQUIRES_NEW   外有事务、inner 抛异常被 outer 捕获 → outer 正常结束,行 outer
  NESTED         外有事务、inner 抛异常被 outer 捕获 → outer 正常结束,行 outer
  SUPPORTS       外有事务、inner 抛异常被 outer 捕获 → outer 结束时 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only;行 (空)
  NOT_SUPPORTED  外有事务、inner 抛异常被 outer 捕获 → outer 正常结束,行 outer+inner
  MANDATORY      外有事务、inner 抛异常被 outer 捕获 → outer 结束时 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only;行 (空)
  NEVER          外有事务、inner 抛异常被 outer 捕获 → outer 正常结束,行 outer

加入外层事务的会让外层提交时抛 UnexpectedRollbackException;REQUIRES_NEW 与 NESTED 只回滚自己。

外面没有事务

实测 · 本机执行输出
  REQUIRED       外面没有事务 → 行 outer+inner;inner 事务活跃=true,inner 连接 18
  REQUIRES_NEW   外面没有事务 → 行 outer+inner;inner 事务活跃=true,inner 连接 18
  NESTED         外面没有事务 → 行 outer+inner;inner 事务活跃=true,inner 连接 18
  SUPPORTS       外面没有事务 → 行 outer+inner;inner 事务活跃=false,inner 连接 18
  NOT_SUPPORTED  外面没有事务 → 行 outer+inner;inner 事务活跃=false,inner 连接 18
  MANDATORY      外面没有事务 → ✗ IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
  NEVER          外面没有事务 → 行 outer+inner;inner 事务活跃=false,inner 连接 18

REQUIRED、REQUIRES_NEW、NESTED 新开事务,SUPPORTS、NOT_SUPPORTED、NEVER 无事务执行,MANDATORY 抛异常。

分步演示

事务传播行为
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9 的 @EnableTransactionManagement + DataSourceTransactionManager + HikariCP + 自己起的 MySQL 26.7.0(innodb);内层方法通过另一个 Bean(Inner)被外层调用,走代理;开发时我曾把分发代码写在 Inner 自己里面,所有传播行为的结果完全相同——那是自调用(见自调用篇),这篇的结果是改成跨 Bean 调用之后跑的
    • 没有验证:NESTED 在不支持 savepoint 的数据库或 JTA 下的行为;读 outer 已写入但未提交的数据时 REQUIRES_NEW 是否会死锁(两个连接争同一行);rollbackFor 对内层异常类型的影响(见下一篇);传播行为与隔离级别的组合;事务超时在内外层的传递
    京ICP备2026067502号-1京公网安备11010502063487号