Spring事务传播级别常见问题

178 阅读4分钟

Spring事务传播级别常见问题

​ Spring 事务传播级别为处理事务在不同方法中调用的场景中事务管理问题而设计,它定义了当前方法在调用其他事务方法时,应当如何处理已有的事务。核心目的是为了协调不同事务的边界和上下文,避免常见的问题,如嵌套事务中事务回滚、事务死锁等问题。本文旨在记录在不同场景下使用嵌套事务可能出现的问题以及解决方法。

先解释一下各个事务传播级别

  1. REQUIRED(默认的事务传播级别):当前方法必须要在允许在一个事务中,如果当前上下文存在事务则加入到该事务中,如果没有事务,则创建一个事务;
  2. REQUIRES_NEW: 当前事务必须允许在一个新事物中,如果不管当前上下文是否有事务存在都将开启一个新事物。
  3. NESTED: 运行在当前上下文事务中,并创建一个子事务(回滚点),在子事务进行回滚时只会回滚子事务中的数据库操作,不会回滚外部事务(外部事务方法要try catch子事务方法,防止异常被抛出)。
  4. SUPPORTS:当前方法支持事务,如果当前上下文有事务,则加入事务,如果没有事务,则以非事务方式运行。
  5. NOT_SUPPORTED:当前方法不在事务中执行,如果上下文存在事务则挂起事务,以非事务方式执行。
  6. NEVER:当前方法不运行在事务中,如果调用时存在事务,则抛出异常。
  7. MANDATORY:当前方法必须存在事务中,如果调用时不存在事务,则抛出异常。

1. 嵌套事务的死锁问题

​ 在使用嵌套事务时,外部存在事务,且方法内调用的事务方法的事务传播级别为REQUIRES_NEW,即内部子事务新发起了一个事务,这时如果外部事务与内部子事务存在操作同一数据时可能会发生死锁。

​ 如下场景:

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handler() {

        TransactionTest test = new TransactionTest();
        test.setId(2);
        test.setName("小红");
        updateById(test);
        try {
            transactionTestService.updateTestById2();
        }catch (Exception e){
            System.out.println("捕获异常");
        }
    }
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void updateTestById2() {

            TransactionTest test = new TransactionTest();
            test.setId(2);
            test.setName("花花");
            updateById(test);
            System.out.println("更新数据完成");
            throw new RuntimeException("抛出异常");
    }

其中handler方法开启事务操作数据2(ID 2为主键),然后调用updateTestById2方法,updateTestById2方法开启了一个独立事务,方法中也对数据2进行操作,就会发生死锁,handler方法在等updateTestById2方法执行完毕然后提交事务,而updateTestById2方法在等待handler方法释放数据2行锁,即发生死锁。解决方法就是updateTestById2方法对事务传播不要设置为REQUIRES_NEW,可以使用默认传播级别REQUIRED,这样updateTestById2事务就会加入到handler事务中,不会发生死锁。

2.在事务回滚管理问题

​ 在业务中经常遇到需要父子事务回滚不相互影响的场景,如父事务需要子事务抛出异常时,只回滚子事务而不回滚父事务中的操作;父事务抛异常而不影响子事务的操作。

  1. 父事务需要子事务抛出异常时,只回滚子事务而不回滚父事务中的场景,可以在子事务使用NESTED的事务传播级别,并捕获子事务方法抛出的异常,这时会只回滚子事务中的数据操作。还可以在子事务使用REQUIRES_NEW事务传播级别,同样的需要对子事务方法进行异常捕获,但是使用REQUIRES_NEW要看看是否存在可能的死锁问题。
  2. 父事务抛异常而不影响子事务的操作,这时只需要在子事务使用REQUIRES_NEW事务传播级别就可以到达这种效果。

在处理事务回滚问题上,最主要的分析方法就是要判断方法与方法之间是否处于同一个事务之中。熟悉事务方法在不同事务传播级别下的事务存在方式,对解决这类问题有极大的帮助。