Spring事务失效的十大坑,你踩过几个?
Spring事务失效的十大坑,你踩过几个?
Spring的@Transactional注解用起来方便,但稍不注意就会掉进“事务失效”的坑里。今天我们就来盘一盘这些常见的失效场景,帮你绕开雷区,写出稳定可靠的事务代码!
1. 非public方法加注解:白忙活
@Transactional默认只对public方法生效。如果你在private、protected或默认权限的方法上加注解,Spring压根不会代理这个方法,事务自然不生效。
例子:
1 | @Transactional |
解决:老老实实改成public方法。
2. 自调用:自己坑自己
在同一个类里,方法A调用加了@Transactional的方法B,事务会失效。因为Spring的事务代理机制是通过AOP动态生成的,直接通过this.方法B()调用会绕开代理对象。
例子:
1 | public void methodA() { |
解决:
- 把方法B拆分到另一个Bean里调用。
- 用
AopContext.currentProxy()获取代理对象再调用:
1 | ((MyService) AopContext.currentProxy()).methodB(); |
3. 异常处理:吃了哑巴亏
Spring默认只在抛出RuntimeException时回滚事务。如果你捕获了异常没抛出去,或者抛的是IOException这类“受检异常”,事务不会回滚。
例子:
1 | @Transactional |
解决:
- 抛RuntimeException,或者在注解里加
rollbackFor = Exception.class。 - 如果必须捕获异常,手动回滚:
1 | TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); |
4. 数据库引擎:硬件拖后腿
用了不支持事务的数据库引擎(比如MySQL的MyISAM),再怎么折腾注解也没用!事务回滚全靠数据库支持,引擎不行直接凉凉。
解决:换InnoDB引擎。
5. 多线程:分头行动
新开线程执行数据库操作时,事务会失效。因为事务绑定在线程上,子线程和主线程的事务上下文是隔离的。
例子:
1 | @Transactional |
解决:
- 避免在事务方法里开线程操作数据库。
- 用分布式事务框架(如Seata)。
6. 传播行为:配置反常识
事务传播行为配置错误可能导致事务不生效。比如用Propagation.NOT_SUPPORTED时,方法根本不开启事务。
例子:
1 | @Transactional(propagation = Propagation.NOT_SUPPORTED) |
解决:根据业务需求选对传播行为,常用的是REQUIRED(有事务加入,没事务新建)。
7. final/static方法:代理拦路虎
final或static方法无法被Spring动态代理,加了@Transactional也无效。
例子:
1 | @Transactional |
解决:别用final/static修饰事务方法。
8. 配置漏了:没开事务开关
忘记在配置类加@EnableTransactionManagement,或者没配事务管理器,注解直接变摆设。
解决:检查配置,确保事务管理器Bean存在。
9. AOP代理失效:自己调自己
如果事务方法被非事务方法调用(比如Controller直接调Dao层),事务不会生效。因为只有通过Spring代理对象调用的方法才会触发事务。
解决:确保事务方法通过Service层代理调用。
10. 嵌套调用:传播行为背锅
外层方法没加事务,内层方法加了@Transactional,但传播行为配置为REQUIRED时,内层方法会直接继承外层的事务状态(如果外层没事务,内层自己开)——但如果是自调用,依然可能失效。
例子:
1 | public void outer() { |
解决:通过代理对象调用内层方法,或拆分成两个Bean。
总结
事务失效的常见原因可以归纳为三类:
- 代理问题:非public方法、自调用、final/static方法。
- 异常处理:未抛出异常、捕获后未回滚。
- 配置问题:传播行为、数据库引擎、事务管理器。
