Spring事务管理是Java应用中确保数据库操作一致性和完整性的关键机制之一。然而,在实际开发中,有时候会遇到Spring事务失效的情况,导致期望的事务行为无法正常发生。本文将深入探讨九种常见的导致Spring事务失效的场景,帮助开发者更好地理解事务管理的细节和注意事项。
场景 1
众所周知,java 的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。 但如果我们在开发过程中,把某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如:
@Service
public class UserService {
@Transactional
private void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
场景 2
有时候,某个方法不想被子类重写,这时可以将该方法定义成 final 的。普通方法这样定义是没问题的,但如果将事务方法定义成 final,例如:
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel){
saveData(userModel);
updateData(userModel);
}
}
如果你看过 spring 事务的源码,可能会知道 spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者 cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
注意:如果某个方法是 static 的,同样无法通过动态代理,变成事务方法。
场景 3
有时候我们需要在某个 Service 类的某个方法中,调用另外一个事务方法,比如:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
我们看到在事务方法 add 中,直接调用事务方法 updateStatus。从前面介绍的内容可以知道,updateStatus 方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。
由此可见,在同一个类中的方法直接内部调用,会导致事务失效。
那么问题来了,如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢?
使用 AopContext.currentProxy() 或者基于 Spring Event 进行事件解耦当然也要注意事务问题
场景 4
在我们平时开发过程中,有个细节很容易被忽略,即使用 spring 事务的前提是:对象要被 spring 管理,需要创建 bean 实例。 通常情况下,我们通过 @Controller、@Service、@Component、@Repository 等注解,可以自动实现 bean 实例化和依赖注入的功能。
如果有一天,你匆匆忙忙地开发了一个 Service 类,但忘了加 @Service 注解,比如:
//@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
场景 5
多线程调用
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
场景 6
表不支持事务
众所周知,在 mysql5 之前,默认的数据库引擎是myisam。
它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比 innodb 更好。
有些老项目中,可能还在用它。
在创建表的时候,只需要把ENGINE参数设置成MyISAM即可
场景 7
异常被忽略 有时候,开发者可能选择忽略掉一个异常,而不重新抛出或处理。这样的做法将导致事务失效,因为Spring事务管理依赖于异常来判断是否需要回滚事务。
@Transactional
public void handleException() {
try {
// 事务操作
throw new RuntimeException("Simulate Exception");
} catch (Exception e) {
// 异常被忽略,事务失效
}
}
在上述示例中,异常被捕获但未重新抛出或处理,导致事务失效。
场景 8
使用错误的事务传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performTransaction() {
// 使用错误的传播行为,可能导致事务失效
}
场景 9
对非受检查异常进行捕获
Spring事务默认只对RuntimeException及其子类进行回滚。如果一个受事务管理的方法抛出了非受检查异常,而异常被捕获了,并且没有重新抛出,事务将不会回滚。
@Transactional
public void performTransaction() {
try {
// 事务操作
throw new Exception("Unchecked Exception");
} catch (Exception e) {
// 非受检查异常被捕获,事务失效
}
}
总结
Spring事务管理是保证应用数据一致性和完整性的重要组成部分,但在实际开发中需要注意避免事务失效的情况。本文详细讨论了九种常见的导致Spring事务失效的场景,包括不是通过Spring容器管理的Bean调用事务方法、异常未被正确捕获或处理、方法内部调用导致的事务失效等。开发者应当牢记这些场景,并在开发过程中注意避免出现事务失效的情况,以确保数据的一致性和完整性。