📌 阅读前提示 :事务阶段收官篇。前面两篇讲了事务怎么开、传播行为怎么决策。本篇是最「实战排错」的一篇------
@Transactional明明加了却不回滚/不生效,到底为什么?我按「源码归因」逐一拆 7 类高频失效场景,每一条都对应一个源码机制。
一、引子:事务不生效,先问三个问题
遇到「事务没回滚」,按这个顺序排查:
- 这个 Bean 被 AOP 代理了吗?(没代理,拦截器根本不介入)
- 方法被拦截器匹配到了吗?(传播行为、异常类型)
- 异常真的触发回滚了吗?(rollbackFor / 异常类型)
下面 7 个场景,全是这三条的具象化。
二、七大失效场景与源码归因
场景 1:同类自调用(最常见)
java
@Service
public class OrderService {
public void create() { this.pay(); } // this 指向原始对象
@Transactional
public void pay() { /* ... */ }
}
归因 :第 14 篇原理------this 是原始对象,绕过了代理,TransactionInterceptor 没介入。
解决 :@EnableTransactionManagement(exposeProxy=true) + AopContext.currentProxy(),或拆到另一个 Bean。
场景 2:方法非 public
java
@Transactional
private void update() { ... } // ★ private 不生效
归因 :Spring AOP(JDK/CGLIB)默认只能拦截 public 方法;CGLIB 重写也只覆盖 public/protected。AbstractFallbackTransactionAttributeSource 对非 public 方法返回 null 事务属性------等于没事务。
场景 3:异常被 catch 吞掉
java
@Transactional
public void update() {
try { dao.update(); } catch (Exception e) { /* 吞掉 */ }
}
归因 :TransactionInterceptor 只有在异常抛出到拦截器 时(completeTransactionAfterThrowing)才回滚。catch 住没抛,拦截器认为「正常返回」→ 提交。
场景 4:抛了 checked Exception
java
@Transactional
public void update() throws IOException { throw new IOException("x"); }
归因 :第 25 篇------默认 rollbackOn 只对 RuntimeException/Error 回滚(见 RuleBasedTransactionAttribute.rollbackOn)。IOException 是 checked,默认不回滚 。
解决 :@Transactional(rollbackFor = Exception.class)。
场景 5:数据库引擎不支持事务
MyISAM 表没有事务;或数据源未配 PlatformTransactionManager。
归因 :底层 Connection 非事务连接,commit/rollback 无效果。
场景 6:传播行为设为 NOT_SUPPORTED / NEVER
java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void update() { ... } // 挂起事务,非事务执行
归因 :第 26 篇------NOT_SUPPORTED 挂起现有事务、以非事务运行;NEVER 直接抛异常。
场景 7:多线程调用
java
@Transactional
public void outer() {
new Thread(() -> inner()).start(); // 新线程没有事务上下文
}
归因 :事务信息存在 ThreadLocal(如 TransactionSynchronizationManager 的资源绑定)。新线程拿不到外层连接,等于无事务。
三、失效场景归因速查表
| # | 场景 | 源码归因 |
|---|---|---|
| 1 | 同类自调用 | this 是原始对象,绕开代理(第14篇) |
| 2 | 非 public 方法 | 事务属性解析对非 public 返回 null |
| 3 | 异常被 catch | 异常未传到拦截器,判定为正常返回 |
| 4 | checked 异常 | 默认 rollbackOn 不含 checked |
| 5 | 引擎/管理器缺失 | 底层非事务连接 |
| 6 | 传播行为不当 | NOT_SUPPORTED/NEVER 非事务 |
| 7 | 多线程 | 事务绑 ThreadLocal,新线程无上下文 |
四、事务失效场景与源码机制图

五、常见误区
| 误区 | 正解 |
|---|---|
| 加了 @Transactional 就一定有事务 | 自调用/非public/异常被吞都可能使事务不生效 |
| 任何异常都回滚 | 仅 RuntimeException/Error 默认回滚 |
| catch 后事务自动回滚 | 异常没抛到拦截器,反而提交 |
| 多线程共享外层事务 | 事务绑 ThreadLocal,跨线程失效 |
| 失败一定是配置问题 | 多数失效是「调用方式」问题(自调用最常见) |
🧪 面试题自测
- 同类自调用为什么让 @Transactional 失效?怎么解决?
- 为什么 private 方法上的 @Transactional 不生效?
- catch 住异常后事务还会回滚吗?为什么?
- 抛 IOException 默认回滚吗?如何让它回滚?
- 多线程调用为什么会丢失事务上下文?
- 如何用源码视角排查「事务不回滚」?
🔧 Debug 小技巧
在 TransactionInterceptor.completeTransactionAfterThrowing 断点 + 在 RuleBasedTransactionAttribute.rollbackOn 断点,分别用「抛 RuntimeException」「抛 IOException」复现,对比回滚决策;再用自调用场景断 createTransactionIfNecessary,会发现根本没进入(因为代理没介入)。
下一篇预告
事务阶段完结。下一篇进入 进阶与实战(第 28 篇) :Spring 核心扩展点全景------BeanFactoryPostProcessor / BeanPostProcessor / Aware / ImportBeanDefinitionRegistrar / FactoryBean 各自在什么时机、能做什么,一张图收齐前面 30 篇的扩展点。
如果这篇对你有帮助,欢迎 点赞 · 收藏 · 关注 三连支持。
Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。