Spring事务传播行为

Spring声明事务事务传播行为

名词解释

事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

区别解释

上文可以看出PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_MANDATORY解释比较明确,一般不会出现歧义
重点解释下PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED的区别.
为了解释方便,假设一下代码为场景,该案例只是表示调用场景,因为缺乏必要的Spring注解和调用方式的不正确实际事务不会生效,具体使用案例参照下文案例.

public void methodA(){
  methodB();
  // do something
}

public void methodB(){
  // do something
}

方法A调用方,方法B被调用方

PROPAGATION_REQUIRED

Spring默认的事务传播行为,如果调用方A有事务,被调用方B就加入这个事务,如果调用方A没有事务,被调用方B就新建一个事务.如果加入的话,调用方A被调用方B是在完全相同的一个事务中,共同回滚,提交.

PROPAGATION_REQUIRES_NEW

无论当前调用方A是否存在事务,被调用方B都新建一个事务,如果调用方A存在事务,则挂起.调用方A被调用方B是完全不同的两个事务.两个事务分别回滚,提交

PROPAGATION_NESTED

如果调用方A存在事务,被调用方B创建一个嵌套事务来运行,如果调用方A不存在事务,被调用方B新建一个事务,所谓嵌套运行可以理解为,调用方A回滚会携带被调用方B一起回滚,被调用方B回滚不会影响调用方A.
--调用方A开始(设置savePointA)
----被调用方B开始(设置savePointB)

----被调用方B结束(失败则回滚到savePointB)
--调用方A结束(成功则提交,失败则回滚到savePointA)
可以看出,
B想提交成功需要两个条件,B成功,A也成功
B想要回滚,A或者B有一个异常就可以了,
A提交只需要A中成功就可以(B抛出的异常需要处理),A异常的时候会共同回滚
A异常会回滚到savePointA,B异常只回滚到savePointB
上文只是本人的理解,Spring真正的实现方式是怎样没有深入了解,以后有机会会补上,有懂的请不吝赐教.

代码验证(举例)

上面解释也许只是对着名词解释假设的,具体是不是这样还需要验证一番

@Service
public class StudentServiceImpl implements StudentService {
    @Resource
    StudentMapper studentMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void methodFather() {
        int insert = studentMapper.insert(Student.builder().name("父方法先").build());
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
        } catch (Exception e) {

        }
        int insert2 = studentMapper.insert(Student.builder().name("父方法后").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void method_NESTED() {
        int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void method_REQUIRES_NEW() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void method_REQUIRED() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
    }
}

上面的代码可以看出,调用方methodFather分别调用了method_NESTED,method_REQUIRES_NEWmethod_REQUIRED三个呗调用方,调用方法的地方都分别使用了trycatch语句,保证子方法的异常不会影响到父方法.

正常运行

Creating a new SqlSession
Registering transaction synchronization for SqlSession 
**注册父事务a1b7bb8**
[org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@192cd917] will be managed by Spring
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 父方法先(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法NESTED(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Creating a new SqlSession
**注册子事务REQUIRES_NEW a1b7bb8**
Registering transaction synchronization for SqlSession 
[org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@16831f94] will be managed by Spring
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法REQUIRES_NEW(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法REQUIRED(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 父方法后(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]

可以看到只有REQUIRES_NEW新建了一个事务,其他都是使用的父事务

父事务异常

将父方法methodFather修改为如下

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void methodFather() {
        int insert = studentMapper.insert(Student.builder().name("父方法先").build());
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
        } catch (Exception e) {

        }
        int insert2 = studentMapper.insert(Student.builder().name("父方法后").build());
        // 父事务异常
        int i = 1 / 0;
    }

运行结果

父事务异常,回滚,加入父事务的REQUIRED和嵌入的NESTED都回滚了,只有开启新事务的REQUIRES_NEW不受影响,成功提交了

REQUIRED子方法异常

去除父方法异常代码
修改method_REQUIRED代码如下

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void method_REQUIRED() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
        //REQUIRED异常测试
        int i = 1 / 0;
    }


结果与父方法异常相同,原因是REQUIRED是加入父事务,它回滚等于父事务回滚

REQUIRES_NEW异常

相同方法,去除REQUIRED中的异常
REQUIRES_NEW子方法加入异常

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void method_REQUIRES_NEW() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
        //REQUIRES_NEW异常测试
        int i = 1 / 0;
    }


除了REQUIRES_NEW异常自己回滚了,其他成功提交

NESTED异常测试

相同方法,去除其他异常
method_NESTED中加入异常

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void method_NESTED() {
        int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
        //NESTED异常测试
        int i = 1 / 0;
    }

因为测试使用的是同一张表,该异常出现的时候会进行锁表,造成REQUIRES_NEW的提交超时,此处将method_NESTED方法调用移到最后执行

可以看到NESTED作为嵌套事务自己回滚了,不影响父事务提交

posted @ 2022-03-09 11:18  長門有希  阅读(567)  评论(0)    收藏  举报