随着SpringBoot在开发中的广泛使用,声明式事务得到了越来越多的认可。一个@Transactional注解就可以搞定事务,可是如果对事务和该注解的认识不足,往往会给开发带来一些bug。
1.一个@Transcational真的就够了吗?
@Transactional
public void testRollBack() throws SQLException {
User user = User.builder().u_name("Tom").age(18).build();
userMapper.insert(user);
// 一些业务代码 产生的检测异常
throw new SQLException();
}
请问这段代码事务会回滚吗?
事实告诉我们,这段代码其实不会回滚该异常。那么为什么呢?原因在于@Trancational注解默认情况下只会回滚运行时异常(RunTimeExcetion)和错误Error
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
这段代码来自org.springframework.transaction.interceptor.DefaultTransactionAttribute
那么在开发中如果需要回滚检测异常怎么办呢?其实只须加上下列代码即可,将异常放大到Exception.
@Transactional(rollbackFor = Exception.class)
平时写代码的时候,sql错了(java.sql.SQLSyntaxErrorException继承于SqlException,非运行时异常),事务不也照常回滚吗?这是因为框架帮我们捕获了该异常,对Aop事务抛出的运行时异常,所以事务回滚了。
2.运行时异常为何不回滚?
@Override
@Transactional(rollbackFor = Exception.class)
public void testRollBack() {
User user = User.builder().u_name("Tom").age(18).build();
userMapper.insert(user);
try {
// 业务代码
throw new RuntimeException("");
} catch (Exception e){
log.warn("警告日志");
}
}
请问这段代码事务会回滚吗?
事实再次告诉我们,事务不会回滚。这是因为Spring提供的事务机制,其实也是try catch 这种形式,当Spring无法捕获到异常时,就会认为这段代码正常,而不会回滚,因此,如果当你的业务需要回滚时,一定要将你捕获的异常再次抛出,让Spring感知你的异常,这样才会回滚。
3.调用一个同类(或父类)的方法为何不回滚?
@Override
public void testRollBack() {
insert();
}
@Transactional(rollbackFor = Exception.class)
public void insert(){
User user = User.builder().u_name("Tom").age(18).build();
userMapper.insert(user);
throw new RuntimeException();
}
实际上这段代码也不会回滚的。这是因为Spring的事务机制是基于Aop的切面编程,而切面是依附于类的,从web层->service层涉及到切面了,因此事务是生效的,而service层内部的调用,是不涉及类与类之间的调用,spring自然也不会感知到Aop。
上述代码是同类的调用,实际上子类调用父类的方法也会生效。
那么如何解决这个问题呢?一种方法是将@Transaction注解移到testRollBack()方法上,另一种方法是将事务类方法做业务代码的下层,放在一个新的类中,用当前类去调用新类中的方法,这样事务就会生效。这种方法适合于业务代码复杂,事务代码可以集中在一处。