Spring事务失效的几种情况及其解决方案
方法权限修饰符不是public
@Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的,而 @Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象,这样也就没办法自动回滚事务了,它的部分实现源码如下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
// 非 public 方法,设置为 null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 后面代码省略....
}
解决方案:
将方法的权限修饰符改为 public 即可
Try/Catch
当程序中出现了 try/catch 代码时,事务不会自动回滚,这是因为 @Transactional 注解在其实现时,需要感知到异常才会自动回滚,而用户自行在代码中加入了 try/catch 之后,@Transactional 就无法感知到异常了,那么也就不能自动回滚事务了。
解决方案:
将异常重新抛出
Final修饰符
spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者 cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
注意:如果某个方法是 static 的,同样无法通过动态代理,变成事务方法。
解决方案:
不使用final修饰符
同一个类内部调用
在同一个类中的方法直接内部调用时,直接调用了 this 对象的方法,没有使用代理对象的方法,因此会导致事务失效。
解决方案
在该 Service 类中注入自己
此时注入的就是代理类
在该 Service 类中使用 AopContext.currentProxy() 获取当前实例的代理对象。
数据库不支持事务
当我们在程序中添加了 @Transactional,相当于给调用的数据库发送了:开始事务、提交事务、回滚事务的指令,但是如果数据库本身不支持事务,比如 MySQL 中设置了使用 MyISAM 引擎,因为它本身是不支持事务的,这种情况下,即使在程序中添加了 @Transactional 注解,那么依然不会有事务的行为,也就不会执行事务的自动回滚了。
解决方案:
设置 MySQL 的引擎为 InnoDB 就可以解决问题了,因为 InnoDB 是支持事务的