Spring 事务失效的8种场景

前言

无论是平常工作还是面试找工作,Spring 事务总是绕不开的一个话题,上一篇文章介绍了下Spring 事务的相关知识,感兴趣的可以看我上一篇文章: Spring 事务基本概念。今天就来讲讲 Spring 事务几种常见的失效场景。

Spring 事务失效的 8 种场景

  • 数据库不支持事务
  • 事务方法未被 Spring 管理
  • 方法没有被 public 修饰
  • 方法用 final 关键字修饰
  • 方法自身调用问题
  • @Transactional 配置的传播特性不支持事务
  • 不正确的捕获异常
  • 异常类型错误

一、数据库不支持事务

众所周知 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 条评论) “”
   
验证码:

相关文章

推荐文章