1084 字
约 3 分钟
6
S07-J01 · 事务:传播机制与失效的六种原因

适用读者:遇到过"加了 @Transactional 但数据没回滚"的人。
前置:S07-B03(AOP 代理)、S07-B05。
实测环境:Spring Boot 3.3.6 / JDK 17 / H2(2026-11-05)。
覆盖旧文:1757(144) 975(89) 2682(174) 976(82) = 489 点。
覆盖扩展:E-S07-011(七种传播行为)、E-S07-012(失效六因)。
上一篇:S07-B06 · Web 层实战:校验、异常、日志、文档、监控
下一篇:S07-J02 · 循环依赖与三级缓存。


1. 一句话结论

@Transactional 也是 AOP:只有"从代理进入"的调用才有事务。所以"自调用"是失效第一因,与 B03 的切面失效同源。

实测(同一个 Service 的两种调用方式):

1. 直接调用抛异常 → boom
   回滚后行数 = 0(期望 0)
2. 自调用抛异常 → inner boom
   行数 = 1(期望 1:未回滚)

同样的 @Transactional 方法:外部调用会回滚(0 行),自调用不回滚(1 行)。


2. 七种传播行为(E-S07-011)

传播 有事务时 无事务时 用途
REQUIRED(默认) 加入 新建 绝大多数场景
REQUIRES_NEW 挂起外层,新建 新建 独立事务(如记日志)
NESTED 保存点(可部分回滚) 新建 子流程失败不影响主流程
SUPPORTS 加入 不用事务 只读查询
NOT_SUPPORTED 挂起 不用 明确不要事务
MANDATORY 加入 抛异常 强制要求外层有事务
NEVER 抛异常 不用 强制不能有事务

判据:"这段失败要不要连累外层?" 连累 → REQUIRED;不连累 → REQUIRES_NEW(独立)或 NESTED(同连接、可部分回滚)。


3. 失效的六种原因(E-S07-012、A-2682/A-976)

# 原因 实测/说明
1 自调用(this.method()) 已实测:不回滚
2 方法不是 public 代理无法拦截
3 异常类型不匹配 默认只回滚 RuntimeException/Error;受检异常不回滚(需 rollbackFor)
4 异常被吞掉 catch 后没抛 → 事务不知道失败
5 跨线程 事务绑定线程(ThreadLocal),异步调用不在同一事务
6 数据库/引擎不支持 MyISAM 无事务(S11 详讲)

第 3 条最隐蔽:受检异常默认不回滚——必须 @Transactional(rollbackFor = Exception.class)。


4. 自调用怎么修

方案 做法 代价
拆到另一个 Bean 让调用走代理 推荐(结构更清晰)
注入自身代理 @Lazy 自注入 / AopContext.currentProxy() 侵入性强
TransactionTemplate 编程式事务 代码更啰嗦,但可控

判据:"这个方法真的需要独立事务吗?" 需要 → 拆 Bean;只是"顺手加个注解" → 别加。


5. 只读事务与超时

属性 作用
readOnly = true 提示优化(部分驱动可路由到只读库)
timeout = 5 超时回滚(秒)
isolation 隔离级别(默认跟随数据库)

6. 五个误区

误区 真相
"加了注解就有事务" 只有走代理才有(实测自调用失效)
"抛任何异常都回滚" 默认只回滚运行时异常
"事务能跨线程" 事务绑定线程
"REQUIRES_NEW 一定更好" 会占用额外连接,可能死锁
"私有方法加注解也行" 代理拦截不到

7. 自检

  1. 实测里为什么自调用不回滚?
  2. 受检异常默认回滚吗?怎么改?
  3. REQUIRES_NEW 与 NESTED 的区别?
  4. 哪两种原因最容易在真实项目里出现?
  5. 修自调用有哪三种方案?

8. 下一篇

S07-J02 · 循环依赖与三级缓存

事务讲完,下一篇处理启动期的问题:两个 Bean 互相依赖时,Spring 为什么报错、三级缓存又在缓存什么。


本篇属于系列《S07 · Spring 框架》。旧文来源:A-1757/975/2682/976;扩展点 E-S07-011/012。回滚对照为 Spring Boot 3.3.6 + H2 实测。

S07-J01 · 事务:传播机制与失效的六种原因
https://www.clxhxhhr.top/posts/5274/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。