@Transactional 抛了异常就一定回滚吗?受检异常呢?异常被 catch 吞掉呢?rollbackFor 和 noRollbackFor 同时命中谁说了算?这篇在 Spring Framework 7.0.9 和 MySQL 26.7.0 上逐个量出来。
抛 RuntimeException → 调用者看到 IllegalStateException 表里剩 0 行 → 回滚 抛 Error(AssertionError) → 调用者看到 AssertionError 表里剩 0 行 → 回滚 抛受检异常 IOException(默认) → 调用者看到 IOException 表里剩 1 行 → 提交 抛受检异常,rollbackFor = Exception.class → 调用者看到 IOException 表里剩 0 行 → 回滚 抛 IOException,rollbackFor = IOException.class → 调用者看到 IOException 表里剩 0 行 → 回滚 抛 IOException,rollbackForClassName → 调用者看到 IOException 表里剩 0 行 → 回滚 抛 RuntimeException,noRollbackFor → 调用者看到 IllegalStateException 表里剩 1 行 → 提交
RuntimeException 与 Error 回滚,受检异常提交;rollbackFor 声明受检异常也回滚。
受检异常包成 RuntimeException 再抛 → 调用者看到 RuntimeException ← IOException 表里剩 0 行 → 回滚 方法内部 catch 吞掉异常 → 调用者看到 方法正常返回 表里剩 1 行 → 提交 吞掉异常,但 setRollbackOnly() → 调用者看到 方法正常返回 表里剩 0 行 → 回滚
包成 RuntimeException 会回滚,catch 吞掉会提交,setRollbackOnly() 手动回滚。
抛 MyChild(rollbackFor = MyChecked.class) → 调用者看到 MyChild 表里剩 0 行 → 回滚 rollbackFor = MyChecked,noRollbackFor = MyChild,抛 MyChild → 调用者看到 MyChild 表里剩 1 行 → 提交 rollbackFor = MyChild,noRollbackFor = MyChecked,抛 MyChild → 调用者看到 MyChild 表里剩 0 行 → 回滚 rollbackFor = RuntimeException,noRollbackFor = MyRuntime,抛 MyRuntime → 调用者看到 MyRuntime 表里剩 1 行 → 提交
两条规则都匹配时,继承层次上更近的一条赢。