Spring 事务在什么情况下会失效?
- 标签:后端、Spring
🏛 业务架构师
Spring 事务失效是线上资损事故的经典来源,我把失效场景按"根因"分成四组记忆:
1. 代理没生效(最常见):
- 同类内部调用:
this.saveOrder()不走代理,事务/切面全部失效——自调用是第一高频坑; - 方法不是
public:private/protected/默认方法不被代理增强; - final/static 方法:CGLIB 无法覆写;
- 类没被容器管理(自己 new 的)或没被代理(
final类)。
2. 异常被吞或类型不匹配:
- 方法内 try-catch 把异常吃了,事务感知不到;
- 抛出受检异常但没配
rollbackFor——默认只回滚 RuntimeException 和 Error,业务自定义受检异常不回滚是隐蔽资损点;
3. 传播与多线程:
REQUIRES_NEW挂起外层、NESTED 保存点语义被误用;- 事务内开新线程执行数据库操作——事务绑定 ThreadLocal 连接,子线程不在同一事务,回滚不了;
4. 底层不支持:MyBatis-PLus 自写 SQL 绕过、存储引擎是 MyISAM 不支持事务、数据源没接入事务管理器。
业务治理建议:这些坑不该靠每个开发者记,我把它做成守则——事务方法统一 rollbackFor = Exception.class、外部调用和线程切换移出事务方法、关键资金操作用集成测试验证回滚。架构师的职责是把失效清单转化为防御性工程规范。
🏗 系统架构师
高频失效点按「原理 → 场景」归四类:
- 代理被绕过:同类内部自调用(this 调用不走代理)、方法是 final/static/private(CGLIB 无法覆写)、对象不是从容器获取(自己 new 的);
- 异常语义不匹配:默认只回滚 RuntimeException 与 Error,受检异常不回滚,需
rollbackFor = Exception.class;catch 后吞掉异常等于不回滚; - 配置错误:传播行为 NOT_SUPPORTED/NEVER/NESTED 挂起事务;多数据源场景事务管理器与数据源错配;数据库 DDL 隐式提交;
- 切面未生效:类没被扫描、注解打在接口方法上而实际用 CGLIB 代理。
生产纪律:资金链路必须有「事务生效性」的验证测试——强制抛异常断言回滚;Code Review 按这四类清单过;上线后用慢 SQL 与连接占用侧面验证事务确实开启。一致性要靠机制保证,而不是靠运气。
💻 全栈开发专家
高频考点,失效场景我按踩坑频率排:
- 自调用:同类
this.methodB()不走代理,最常见,没有之一 - 方法非 public:代理拦不到
- 异常被 catch 吞掉:切面感知不到,正常提交
- 默认只回滚 RuntimeException/Error:受检异常要显式
@Transactional(rollbackFor = Exception.class) - 传播行为配错:NOT_SUPPORTED/NEVER 直接没事务
- Bean 没被容器管:手动
new的对象没代理 - 多线程:事务绑定 ThreadLocal 的连接,子线程里拿不到同一连接
- 数据库不支持:MyISAM 没事务
- rollbackFor 与异常类型不匹配
排查三板斧:开 logging.level.org.springframework.transaction=DEBUG 看有没有 "Participating in existing transaction";断点 TransactionInterceptor#invoke 确认切面进入;确认注入的是代理对象(类名带 $$)。
预防上我用 ArchUnit 写了条规则:禁止同类内部调用带事务注解的方法,把第 1 条在 CI 阶段拦住——治过事故的人都会把防线前移。