在日常开发中,Spring事务管理是保证数据一致性的重要手段,但你是否遇到过明明加了@Transactional
注解,事务却“神秘失效”的情况?这种问题不仅让新手抓狂,连老司机也偶尔翻车。本文总结了12种常见的Spring事务失效场景,结合代码示例和解决方案,帮你彻底避开这些坑!
一、访问权限问题
失效原因:Spring事务基于AOP代理实现,默认要求被代理方法必须是public
。如果方法权限是private
、default
或protected
,事务会直接失效。
示例代码:
@Service
public class UserService {
@Transactional
private void add(UserModel userModel) { // 错误:private方法
saveData(userModel);
}
}
解决方案:确保事务方法为public
。
二、final或static方法
失效原因:final或static方法无法被动态代理类重写,导致事务失效。
示例代码:
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel) { // 错误:final方法
saveData(userModel);
}
}
解决方案:避免在事务方法上使用final
或static
。
三、方法内部调用
失效原因:在同一个类中,非事务方法A调用事务方法B时,实际调用的是原对象的方法,而非代理对象,导致事务失效。
假设有一个类 MyService
,其中有一个事务方法 transactionalMethod()
和一个非事务方法 nonTransactionalMethod()
:
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// 事务逻辑
}
public void nonTransactionalMethod() {
// 非事务逻辑
this.transactionalMethod(); // 直接调用事务方法
}
}
在这种情况下,nonTransactionalMethod()
中直接调用 transactionalMethod()
时,事务不会生效。因为 this
指代的是当前对象实例,而不是 Spring 生成的代理对象。
解决方案:
1.使用 AOP 的 AopContext
通过AopContext.currentProxy()
获取代理对象,然后通过代理对象调用事务方法:
@Service
public class MyService {
@Autowired
private ApplicationContext applicationContext;
@Transactional
public void transactionalMethod() {
// 事务逻辑
}
public void nonTransactionalMethod() {
// 非事务逻辑
MyService proxy = (MyService) AopContext.currentProxy();
proxy.transactionalMethod(); // 通过代理对象调用事务方法
}
}
2.注入自身
通过注入自身的方式,让 Spring 的代理对象被注入到类中,然后通过代理对象调用事务方法:
@Service
public class MyService {
@Autowired
private MyService myService;
@Transactional
public void transactionalMethod() {
// 事务逻辑
}
public void nonTransactionalMethod() {
// 非事务逻辑
myService.transactionalMethod(); // 通过注入的代理对象调用事务方法
}
}
四、未被Spring管理
失效原因:如果类没有@Service
、@Component
等注解,Spring不会创建代理对象,事务自然失效。
示例代码:
// @Service // 错误:未添加注解
public class UserService {
@Transactional
public void add(UserModel userModel) { ... }
}
解决方案:确保类被Spring管理。
五、多线程调用
失效原因:不同线程使用不同的数据库连接,事务无法跨线程同步。
示例代码:
@Transactional
public void add(UserModel userModel) {
new Thread(() -> {
roleService.doOtherThing(); // 新线程中的事务独立
}).start();
}
解决方案:避免在多线程中调用事务方法,或用异步事务框架(如@Async
配合事务)。
六、表不支持事务
失效原因:MySQL的MyISAM引擎不支持事务,而InnoDB支持。
建表语句:
CREATE TABLE `user` (...) ENGINE=MyISAM; // 错误:MyISAM引擎
解决方案:使用InnoDB引擎。
七、错误的传播特性
失效原因:错误设置propagation
参数可能导致事务不生效。例如,Propagation.NEVER
要求不能存在事务,否则抛异常。
示例代码:
@Transactional(propagation = Propagation.NEVER) // 错误:当前存在事务
public void add(UserModel userModel) { ... }
解决方案:根据业务场景选择正确的传播特性(默认REQUIRED
)。
八、吞掉异常
失效原因:手动捕获异常但未抛出,Spring认为程序正常执行,不会回滚。
示例代码:
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
} catch (Exception e) {
// 吞掉异常,事务不回滚!
}
}
解决方案:抛出RuntimeException
或在catch
中手动回滚。
九、抛错异常类型
失效原因:Spring默认只回滚RuntimeException
和Error
,普通Exception
不会触发回滚。
示例代码:
@Transactional
public void add(UserModel userModel) throws Exception {
throw new Exception("普通异常"); // 事务不回滚!
}
解决方案:配置@Transactional(rollbackFor = Exception.class)
。
十、自定义回滚异常
失效原因:若rollbackFor
指定了特定异常,但实际抛出其他异常,事务不会回滚。
示例代码:
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) {
throw new SQLException(); // 不属于BusinessException,不回滚!
}
解决方案:设置rollbackFor = Throwable.class
覆盖所有异常。
十一、嵌套事务回滚问题
失效原因:嵌套事务(Propagation.NESTED
)中,若内层异常未被捕获,外层事务会整体回滚。
示例代码:
@Transactional
public void add(UserModel userModel) {
userMapper.insert(userModel);
roleService.doNestedTask(); // 内层事务抛异常,外层也回滚!
}
// RoleService中
@Transactional(propagation = Propagation.NESTED)
public void doNestedTask() { ... }
解决方案:在内层事务中捕获异常并处理,避免异常传递到外层。
十二、未开启事务
失效原因:传统Spring项目需手动配置事务管理器,若忘记配置,事务不生效。
解决方案:
- SpringBoot项目自动配置事务,无需额外处理。
- 传统Spring项目需在XML中配置事务管理器和AOP。
总结
Spring事务失效的常见原因可归结为代理机制、异常处理、配置错误三类。在实际开发中,建议:
- 使用
public
非final方法。 - 统一异常处理逻辑,避免吞异常。
- 通过日志或调试工具检查事务是否生效。
掌握这些技巧后,面试官再问“事务为什么失效”,你就能从容应对了!