Spring之事务传播行为

一、概念

首先简单了解一下Spring中事务传播行为是什么?听起来很高端,但是真正用起来的时候,稍有不慎,就会让自己陷入困境之中,所以在使用之前,我们必须要十分耐心认真的学习它。
从名字理解起来,事务传播行为,既然为传播就肯定发生在两个实体之间,否则单个实体又如何发生行为呢。通俗点讲就是“一个巴掌拍不响”。下面进入正规话题。

  • 事务传播行为主要用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务中,该事务如何传播。这个概述可能不好理解,换句话就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

下面用代码+文字说明解释上面的概念。

@Transaction(Propagation=XXX)
public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
注意:methodA和methodB都加了事务。methodA()也可以不用开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用

二、Spring中七种事务传播行为

通过上面伪代码加文字解释了解到事务传播行为的相关概念,下面就要学习事务传播行为的类型和运行机制。

事务传播行为类型 解释说明
Propagation_Required 表示被修饰的方法必须运行在事务中。如果当前方法没有事务,则就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。此类型是最常见的默认选择
Propagation_Supports 表示被修饰的方法不需要事务上下文。如果当前方法存在事务,则支持当前事务执行;如果当前没有事务,就以非事务方式执行。
Propagation_Mandatory 表示被修饰的方法必须在事务中运行。如果当前事务不存在,则会抛出一个异常。
Propagation_Required_New 表示被修饰的方法必须运行在它自己的事务中。一个新的事务会被启动。如果调用者存在当前事务,则在该方法执行期间,当前事务会被挂起。
Propagation_Not_Supported 表示被修饰的方法不应该运行在事务中。如果调用者存在当前事务,则该方法运行期间,当前事务将被挂起。
Propagation_Never 表示被修饰的方法不应该运行事务上下文中。如果调用者或者该方法中存在一个事务正在运行,则会抛出异常。
Propagation_Nested 表示当前方法已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立与当前事务进行单独地提交或者回滚。如果当前事务不存在,那么其行为与Propagation_Required一样。

验证

Propagation_Required

  • 调用者方法不存在事务传播行为
  1. 调用者方法内部存在异常时,被调用者方法均存在事务,那么结果如何呢?
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) {
        classMapper.insertClass(classDo);
        System.out.println("----------------------->Class插入成功!");
    }

单元测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class PropagationTest {

    private final static StudentDo studentDo = new StudentDo();

    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(1);
        studentDo.setStudentName("student1");
        studentDo.setAddress("测试");

        classDo.setClassName("class_1");
        classDo.setClassNo("Class01");
    }
    @Autowired
    private StudentService studentService;

    @Autowired
    private ClassService classService;

    @Test
    public void insertTest() {
        studentService.insertStudent(studentDo);
        classService.insertClass(classDo);
        
    }
}

在这里插入图片描述
结果:两条数据均被插入数据库。由于外部方法并没有开启事务,所以内部方法均在自己的事务提交或者回滚,因此外部方法中存在异常,内部方法事务不会回滚。

  1. 被调用者均存在事务,而在被调用者中存在异常,那么结果如何?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

 //此方法中抛出异常
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }

单元测试代码
private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("student2");
        studentDo.setAddress("测试2");

        classDo.setClassName("class_2");
        classDo.setClassNo("Class02");
    }
@Test
    public void insertExceptionTest() throws CustomException {
        studentService.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }

在这里插入图片描述
结果 第一数据成功插入,第二条数据因异常存在,事务回滚。内部方法均在各个的事务中运行,class事务回滚,student数据不会受到影响。
在这里插入图片描述
结合1和2我们可以得出结论1:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation_Required修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰

  • 2.调用者开启事务传播行为
内部方法同上	
//单元测试方法
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionThrowsTest() throws CustomException {
        studentService.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }

结果:内部方法虽然存在事务传播行为,但是外部方法也存在事务且使用Propagation.REQUIRED修饰,所有内部方法不会新建事务,直接运行在当前事务中,所以student、class均会被回滚。

  • 3.调用者开启事务传播行为,但是捕获内部方法异常
 /**
     * 内部方法发生异常情况,外部方法即使捕获处理该异常,依然数据会被回滚
     */
    @Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

结果:外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。同2一样,调用者方法执行操作和被调用者中的方法操作结果均被回滚。

Propagation_Supports

	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertSupportsTest() {
        studentService.insertStudent(studentDo);
    }

解释:如果单纯的调用insertStudent()方法,则以非事务执行,即使后面存在异常情况,执行操作结果不会触发事务回滚机制。当调用insertSupportsTest()方法时,该方法以REQUIRED修饰,则会新建一个事务,内部调用insertStudent()方法,所以insertStudent()会加入到当前事务中执行。

Propagation_Mandatory

@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertSupportsTest() {
        studentService.insertStudent(studentDo);
    }

在这里插入图片描述
解释结果:MANDATORY表示被修饰的方法必须在事务中运行。当单独调用insertStudent时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用insertSupportsTest时,insertStudent则加入到insertSupportsTest的事务中,事务地执行。

Propagation_Required_New

表示被修饰的方法必须运行在它自己的事务中。一个新的事务会被启动。如果调用者存在当前事务,则在该方法执行期间,当前事务会被挂起。

private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("requireNew");
        studentDo.setAddress("requireNew");
    }
	@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }
    
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

结果解析:insertStudent(),insertClassByException()方法执行时,外部方法事务被挂起,内部方法会新建事务,直至该方法执行结束,恢复外部方法事务执行。两者之间事务存在隔离性,insertClassByException()方法遇到异常,触发事务回滚机制,但insertStudent()执行结果并受到影响。
如图所示:
在这里插入图片描述
在这里插入图片描述

Propagation_Not_Supported

表示被修饰的方法不应该运行在事务中。如果调用者存在当前事务,则该方法运行期间,当前事务将被挂起。

 private final static ClassDo classDo = new ClassDo();
    static {

        classDo.setClassName("notSupport");
        classDo.setClassNo("notSupport");
    }
	@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

结果解释:即使外部方法开启事务,但是insertClassByException()执行,当前事务会挂起,not_support以非事务方式运行,所以即使遇到异常情况,执行结果也不会触发回滚。
在这里插入图片描述

Propagation_Never

表示被修饰的方法不应该运行事务上下文中。如果调用者或者该方法中存在一个事务正在运行,则会抛出异常。

	@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

 @Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
    }

结果如图:
在这里插入图片描述

Propagation_Nested

表示当前方法已经存在一个事务,那么该方法将会在嵌套事务中运行。
嵌套的事务可以独立与当前事务进行单独地提交或者回滚。
如果当前事务不存在,那么其行为与Propagation_Required一样。
嵌套事务的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

  • 1.外部未开启事务时,内部方法则新建事务执行
	private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("NESTED");
        studentDo.setAddress("NESTED");

        classDo.setClassName("NESTED");
        classDo.setClassNo("NESTED");
    }
	@Test
    public void insertTest() {
        studentService.insertStudent(studentDo);
        classService.insertClass(classDo);
        throw new RuntimeException();

    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) {
        classMapper.insertClass(classDo);
        System.out.println("----------------------->Class插入成功!");
    }

结果
在这里插入图片描述
在这里插入图片描述

  • 2.外部方法开启事务:
  • 如果外部方法发生异常,则内部事务一起发生回滚操作;
  • 如果外部无异常情况,内部被调用方法存在异常情况,则内部方法独立回滚(疑问点???我用以下实例验证,但是外部方法也一样被回滚了,请各位大佬给与解答);
//单测代码
	private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("NESTED_InnerException");
        studentDo.setAddress("NESTED_InnerException");

        classDo.setClassName("NESTED_InnerException");
        classDo.setClassNo("NESTED_InnerException");
    }
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionThrowsTest() throws CustomException {
        studentMapper.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }
//NESTED事务传播行为
	@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new RuntimeException();
    }

源代码传送门:gitHub仓库

原创文章 24 获赞 36 访问量 3万+

猜你喜欢

转载自blog.csdn.net/xuan_lu/article/details/106006755