bigywater.tech
Spring · 162

UnexpectedRollbackException:marked as rollback-only 是怎么触发的

事务里 catch 了异常却还是报 Transaction silently rolled back because it has been marked as rollback-only?哪些写法会触发,哪些不会,表里到底剩什么?这篇用 H2 与 DataSourceTransactionManager 逐个复现。

true
内层 REQUIRED 抛异常、外层 catch 后,isRollbackOnly() 的值,可以在提交之前用它发现问题
只剩 outer
inner 用 REQUIRES_NEW 或 NESTED 时,outer 正常提交,表里只有 outer 的那一行
不抛
outer 自己调用 setRollbackOnly() 后正常返回,静默回滚,没有 UnexpectedRollbackException

触发

实测 · 本机执行输出
  [s1] outer 开着事务,调用 inner(REQUIRED)抛 RuntimeException,outer 把它 catch 掉后正常返回 → 抛出 UnexpectedRollbackException:Transaction rolled back because it has been marked as rollback-only;表 t 里的行:[]
    在 catch 之后、方法返回之前,currentTransactionStatus().isRollbackOnly() = true
  [s7] 同 s1,并在返回前打印 isRollbackOnly → 抛出 UnexpectedRollbackException:Transaction rolled back because it has been marked as rollback-only;表 t 里的行:[]

内层标记共享事务回滚,外层吞掉异常却想提交。

不触发

实测 · 本机执行输出
  [s2] inner 用 REQUIRES_NEW,抛 RuntimeException,outer catch 掉 → outer 正常返回;表 t 里的行:[outer]
  [s3] inner 用 noRollbackFor = IllegalStateException,抛 IllegalStateException,outer catch 掉 → outer 正常返回;表 t 里的行:[inner, outer]
  [s4] inner 就是 outer 自己的另一个方法(自调用,没有代理),抛 RuntimeException,outer catch 掉 → outer 正常返回;表 t 里的行:[inner, outer]
  [s5] inner 用 NESTED(保存点),抛 RuntimeException,outer catch 掉 → outer 正常返回;表 t 里的行:[outer]
  [s6] inner(REQUIRED)抛检查异常 Exception,outer catch 掉 → outer 正常返回;表 t 里的行:[inner, outer]

REQUIRES_NEW、NESTED、noRollbackFor、自调用、检查异常:outer 都能正常提交,表里结果各不相同。

自己标记

实测 · 本机执行输出
  [s8] outer 自己调用 setRollbackOnly() 后正常返回,没有任何 inner → outer 正常返回;表 t 里的行:[]
  [s9] outer 在 catch 里把 inner 的异常重新抛出 → 抛出 IllegalStateException:inner 失败;表 t 里的行:[]

setRollbackOnly 与重新抛出都不会抛 UnexpectedRollbackException。

分步演示

事务
本步的对象

    本文用到的工具与边界

    • 本机 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 解析;数字每次运行会有小幅抖动(Spring Boot 4.0.8 的 starter-jdbc 加 H2 内存库,DataSourceTransactionManager;表 t(name);每个场景前清空表,通过反射调用 outer 的方法,捕获其抛出的异常)
    • 没有验证:JPA 事务管理器(JpaTransactionManager)下的行为;@TransactionalEventListener 与事件回调里的回滚标记;global-rollback-on-participation-failure 设置;响应式事务管理器;Spring 6 以前信息里带「silently」的写法差异;MySQL 上提交失败后的状态
    京ICP备2026067502号-1京公网安备11010502063487号