Spring事务什么情况下会失效

  1. 在方法中捕获异常并没有抛出去

    spring控制事务是基于AOP机制,基于AOP的环绕通知的方式,如果方法抛出了异常给Spring框架,就会进行事务回滚,如果我们利用try…catch捕获了异常,异常不会抛出,也就不会进行事务回滚。

  2. 非事务方法调用事务方法

    在非事务方法内部调用事务方法,尽管这个方法上面加了@Transactional 注解开启了事务,但因为并不是代理对象调用此方法,而是直接调用了 this 对象的方法,所以事务也会失效。因为spring是通过aop的方式,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。

    假设有一个UserService类,其中包含一个事务方法saveUser()和一个非事务方法updateUser():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Service
    public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void saveUser(User user) {
    userMapper.save(user);
    }

    public void updateUser(User user) {
    user.setName("Updated Name");
    saveUser(user); // 在非事务方法中调用事务方法
    }
    }

    在上面的例子中,updateUser()方法是一个非事务方法,它调用了saveUser()方法,而saveUser()方法是一个带有@Transactional注解的事务方法。

    由于updateUser()方法直接调用了saveUser()方法,而不是通过代理对象调用,因此事务将会失效。

  3. 事务方法调用事务方法

    针对上面的例子,为了解决这个问题,可以将updateUser()方法也设置为事务方法,以确保事务能够生效:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service
    public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void saveUser(User user) {
    userMapper.save(user);
    }

    @Transactional
    public void updateUser(User user) {
    user.setName("Updated Name");
    saveUser(user); // 在事务方法中调用事务方法
    }
    }

    通过将updateUser()方法也设置为事务方法,就可以确保事务能够在方法调用时生效。

    这样确实能解决,但是这样有一个弊端,看下面代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service
    public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void saveUser(User user) {
    userMapper.save(user);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUser(User user) {
    user.setName("Updated Name");
    saveUser(user); // 在事务方法中调用事务方法
    }
    }

    如代码所示,利用事务的传播机制想在updateUser上面利用注解开启一个事务,但是由于updateUser不是代理对象调用的,所以并不会成功开启新事务。

    我们可以通过把自己注入到Spring容器中,然后通过自己调用要被事务管理的方法,或者使用 AopContext.currentProxy() 获取代理对象,然后通过代理对象来调用即可。

  4. @Transactional注解标记的方法不是public

  5. 抛出的异常与rollbackFor指定的异常不匹配,默认@Transactional中rollbackFor指定的异常为RuntimeException,通过指定rollbackFor的异常类型,表示只当抛出该类型的异常时才回滚,当抛出其他类型异常则不回滚,事务也就失效。

    1
    2
    //比如配置发生空指针异常时候才回滚,而其他运行时异常事务就会失效
    @Transactional(rollbackFor = NullPointerException.class)
  6. 数据库存储引擎本身不支持事务,如Mysql的MyISAM 引擎

  7. Spring事务的传播特性导致事务失效,比如NEVER、NOT_SUPPORTED

    我们在使用@Transactional注解时,是可以指定propagation参数的。该参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:

    • REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。

    • SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

    • MANDATORY 当前上下文中必须存在事务,否则抛出异常。

    • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

    • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行

    • NEVER 如果当前上下文中**存在事务,则抛出异常,**否则在无事务环境上执行代码。

    • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。