无论是平常工作还是面试找工作,Spring 事务总是绕不开的一个话题,上一篇文章介绍了下Spring 事务的相关知识,感兴趣的可以看我上一篇文章: Spring 事务基本概念。今天就来讲讲 Spring 事务几种常见的失效场景。
一、数据库不支持事务
众所周知 Spring 事务本质是数据库的事务,Spring事务生效的前提是所连接的数据库要支持事务,例如,如果使用的数据库为MySQL,并且选用了MyISAM存储引擎,则Spring的事务就会失效。
二:事务方法未被 Spring 管理
如果事务方法所在的类没有加载到Spring IOC容器中,也就是说,事务方法所在的类没有被Spring管理,则Spring事务会失效,示例如下。
//@Service
public class UserService {
@Transactional
privaet void add(User user){
save(user);
}
}如上所示:类上没有开启@Service注解,导致实例没有加载到Spring IOC容器中,就会造成add()方法的事务在Spring中失效。
三、方法没有被 public 修饰
如果事务所在的方法没有被public修饰,此时Spring的事务会失效,例如,如下代码所示。
@Service
public class UserService {
@Transactional
privaet void add(User user){
save(user);
}
}add()方法的访问权限被定义成了private,这样会导致事务失效,因为Spring要求被代理方法必须是public的。
四、方法用 final 关键字修饰
如下事务方法被修饰为 final,则会导致事务失效。
@Service
public class UserService {
@Transactional
public final void add(User user){
save(user);
}
}原因是事务底层实现使用的Aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而不能添加事务功能。
五、方法自身调用问题
示例一:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void add(User user) {
userDao.insertUser(user);
updateStatus(user);
}
@Transactional
public void updateStatus(User user) {
doSameThing();
}
}示例二:
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void add(User user) {
userDao.insertUser(user);
updateStatus(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateStatus(User user) {
doSameThing();
}
}示例一中有两个方法,add() 非事务方法调用 updateStatus() 事务方法
示例二中两个方法都加上了 @Transactional 注解,但是updateStatus() 方法@Transactional注解中 propagation = Propagation.REQUIRES_NEW,意思是开启一个新的事务,不受外部事务影响,这里涉及到了 Spring 事务的传播机制。
上述两种情况都会导致事务失效,出现这个的问题根本原因在于AOP的实现原理。由于@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,而自己调用自己的过程,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题。
解决办法:(UserService)AopContext.currentProxy().updateStatus(user); 通过AopContext获取当前类的代理类,直接通过代理类调用方法
六、@Transactional 配置的传播特性不支持事务
我们知道@Transactional注解中的 propagation 属性用于设置传播特性,Spring 支持7种类型的传播特性,并不是全都支持事务。
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
privaet void add(User user){
save(user);
}
}如上所示:propagation = Propagation.NEVER 设置这种类型的传播特性则不支持事务,如果有事务会抛出异常。
七、不正确的捕获异常
@Service
public class UserService {
@Transactional
privaet void add(User user){
try {
save(user);
updateSataus(userModel);
} catch (Exception e) {
e.printStackTrace();
}
}
}这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。如果想要Spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
八、异常类型错误
即使开发者没有手动捕获异常,但如果抛的异常不正确,spring 事务也不会回滚。
@Service
public class UserService {
@Transactional
privaet void add(User user){
try {
save(user);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。因为 Spring 事务默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常)不会回滚。
解决办法就是通过 @Transactional 注解的 rollbackFor 属性定义可能会抛出的异常类型。
总结:本文列举了 8 种 Spring事务失效的场景,其实这 8 种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等。其实发生最多就是同一类中方法调用、不正确的捕获异常、异常类型错误。除此之外还有其他场景也会导致事务失效,例如:多线程调用、方法被 static 修饰等等。
| 留言与评论(共有 0 条评论) “” |