前言:
上篇文章中讲到了Spring事务的传播机制,顺便谈了下事务在什么情况下会失效,spring事务失效有多方面的原因,上篇文章讲的失效情况比较浅,所以决定单出一篇文章讲一下。
失效原因
一:数据库引擎不支持事务。
以mysql为例,不同的数据引擎对事务的支持情况是不同的。
MyISAM: 不支持事务
InnoDB: 支持事务,如果想使用事务需要更换到innodb引擎(mysql5.5之后版本默认是innodb)
二:没通过spring 动态代理来获取支持事务的实现类
导致这个问题的情况有很多种。下面写点示例
1. 事务方法被final、static、private修饰
当方法被 final、static、private 修饰时,无论是使用 JDK 动态代理还是 CGLIB 代理,这些方法都不能被 Spring 代理。
以下来自 Spring 官方文档:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
中文:在使用代理时,应该只将 @Transactional 注解应用于具有公共可见性的方法。如果您确实为受保护、私有或包可见的方法添加了 @Transactional 注解,虽然不会引发错误,但被注解的方法并不会展示配置的事务设置。如果您需要注解非公共方法,请考虑使用 AspectJ(见下文)。
2. 调用方法方式导致未被spring代理
调用事务方法时需要注意使用spring代理方式,同类中调用不能直接使用this.method()方式。
错误示例: 这种情况下不会执行事务
正确示例: 同类中调用需要先通过applicationContext.getBean()获取当前类的bean对象,通过bean对象调用事务方法。
三:事务传播机制配置不正确
当事务的传播机制选择的是requires_new、not_supported、never时当前事务不生效。
四:异常类型未正确处理
Spring 事务默认仅在运行时遇到非检查型异常 (unchecked exceptions,如 RuntimeException 及其子类) 时执行回滚。如果配置不当,例如方法抛出检查型异常(如 IOException)而没有在 @Transactional 注解中声明回滚,那么事务不会回滚,导致事务失效。
这种情况可以使用自定义回滚异常方式。
1. 自定义回滚异常:
2.捕获异常之后再抛出RuntimeException异常
不推荐,一是有点脱裤子放屁了,二是抛出具体的异常信息容易定位问题。
五:异步方法调用:
当在 Spring 中进行异步调用(如使用 @Async 注解)时,可能导致事务失效。这是因为异步方法将在另一个线程中运行, Spring 事务是基于线程绑定的,因此原始线程的事务上下文无法传递到新线程中。
所以可以给异步方法新建一个事务,但是需要注意此事务是与上层事务分离的,异步方法内如果进行了回滚,上层事务时不会回滚的。
如果需要解决这个问题,可以使用下述方法:
@Service
public class UserServiceImpl implements IUserService {
@Resource
ApplicationContext applicationContext;
@Transactional
//@Transactional(rollbackFor = Exception.class) 或者直接抛出Exception捕获通用异常
public void saveUser(User order) throws IOException {
//业务逻辑
UserServiceImpl bean = applicationContext.getBean(UserServiceImpl.class);
try {
bean.asyncMethod();
} catch (Exception e) {
// 当异步方法抛出异常时,捕获此异常,并将其转抛为 RuntimeException,以触发 parentMethod 的事务回滚
throw new RuntimeException("Async method failed", e);
}
// saveUser 的其他业务逻辑
}
@Async
public void asyncMethod() {
UserServiceImpl myService = applicationContext.getBean(UserServiceImpl.class);
myService.childMethod();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
// 异步方法执行的逻辑
// ...
// 在需要回滚事务的情况下抛出异常
throw new RuntimeException("Async method exception");
}
}
为方便理解,这里贴上gpt对这段代码的解析
原理:就是通过异步方法回滚时,抛出RuntimeException异常。致使上层事务也触发回滚事件,解决异步方法事务不生效问题。