Spring事务失效的十大坑,你踩过几个?

Spring的@Transactional注解用起来方便,但稍不注意就会掉进“事务失效”的坑里。今天我们就来盘一盘这些常见的失效场景,帮你绕开雷区,写出稳定可靠的事务代码!


1. 非public方法加注解:白忙活

@Transactional默认只对public方法生效。如果你在private、protected或默认权限的方法上加注解,Spring压根不会代理这个方法,事务自然不生效。
​例子​​:

1
2
@Transactional  
private void saveData() { /* 操作数据库 */ }

解决:老老实实改成public方法。


2. 自调用:自己坑自己

在同一个类里,方法A调用加了@Transactional的方法B,事务会失效。因为Spring的事务代理机制是通过AOP动态生成的,直接通过this.方法B()调用会绕开代理对象。
​例子​​:

1
2
3
4
5
public void methodA() {  
this.methodB(); // 事务无效!
}
@Transactional
public void methodB() { /* 操作数据库 */ }

解决

  • 把方法B拆分到另一个Bean里调用。
  • AopContext.currentProxy()获取代理对象再调用:
1
((MyService) AopContext.currentProxy()).methodB();  

3. 异常处理:吃了哑巴亏

Spring默认只在抛出RuntimeException时回滚事务。如果你捕获了异常没抛出去,或者抛的是IOException这类“受检异常”,事务不会回滚。
​例子​​:

1
2
3
4
5
6
7
8
@Transactional  
public void save() {
try {
insertData();
} catch (Exception e) {
// 吞了异常,事务不回滚!
}
}

解决

  • 抛RuntimeException,或者在注解里加rollbackFor = Exception.class
  • 如果必须捕获异常,手动回滚:
1
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  

4. 数据库引擎:硬件拖后腿

用了不支持事务的数据库引擎(比如MySQL的MyISAM),再怎么折腾注解也没用!事务回滚全靠数据库支持,引擎不行直接凉凉。
​解决​​:换InnoDB引擎。


5. 多线程:分头行动

新开线程执行数据库操作时,事务会失效。因为事务绑定在线程上,子线程和主线程的事务上下文是隔离的。
​例子​​:

1
2
3
4
@Transactional  
public void asyncSave() {
new Thread(() -> userDao.update()).start(); // 子线程操作无事务!
}

解决

  • 避免在事务方法里开线程操作数据库。
  • 用分布式事务框架(如Seata)。

6. 传播行为:配置反常识

事务传播行为配置错误可能导致事务不生效。比如用Propagation.NOT_SUPPORTED时,方法根本不开启事务。
​例子​​:

1
2
@Transactional(propagation = Propagation.NOT_SUPPORTED)  
public void update() { /* 无事务! */ }

解决:根据业务需求选对传播行为,常用的是REQUIRED(有事务加入,没事务新建)。


7. final/static方法:代理拦路虎

final或static方法无法被Spring动态代理,加了@Transactional也无效。
​例子​​:

1
2
@Transactional  
public static void saveStatic() { /* 事务无效! */ }

解决:别用final/static修饰事务方法。


8. 配置漏了:没开事务开关

忘记在配置类加@EnableTransactionManagement,或者没配事务管理器,注解直接变摆设。
​解决​​:检查配置,确保事务管理器Bean存在。


9. AOP代理失效:自己调自己

如果事务方法被非事务方法调用(比如Controller直接调Dao层),事务不会生效。因为只有通过Spring代理对象调用的方法才会触发事务。
​解决​​:确保事务方法通过Service层代理调用。


10. 嵌套调用:传播行为背锅

外层方法没加事务,内层方法加了@Transactional,但传播行为配置为REQUIRED时,内层方法会直接继承外层的事务状态(如果外层没事务,内层自己开)——但如果是自调用,依然可能失效。

​例子​​:

1
2
3
4
5
public void outer() {  
inner(); // 内层事务可能不生效!
}
@Transactional
public void inner() { /* 操作数据库 */ }

解决:通过代理对象调用内层方法,或拆分成两个Bean。


总结

事务失效的常见原因可以归纳为三类:

  1. 代理问题:非public方法、自调用、final/static方法。
  2. 异常处理:未抛出异常、捕获后未回滚。
  3. 配置问题:传播行为、数据库引擎、事务管理器。