适用读者:遇到过"加了
@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. 自检
- 实测里为什么自调用不回滚?
- 受检异常默认回滚吗?怎么改?
REQUIRES_NEW与NESTED的区别?- 哪两种原因最容易在真实项目里出现?
- 修自调用有哪三种方案?
8. 下一篇
S07-J02 · 循环依赖与三级缓存
事务讲完,下一篇处理启动期的问题:两个 Bean 互相依赖时,Spring 为什么报错、三级缓存又在缓存什么。
本篇属于系列《S07 · Spring 框架》。旧文来源:A-1757/975/2682/976;扩展点 E-S07-011/012。回滚对照为 Spring Boot 3.3.6 + H2 实测。