Spring事务 - 事务传播机制

Spring事务 - 事务传播机制 

   概要

   在Java的Spring框架中,事务管理是保证应用数据一致性和可靠性的关键。Spring提供了灵活的事务传播机制,它定义了事务边界,以及在嵌套方法调用时如何处理事务。

   事务的传播机制,顾名思义就是多个事务方法之间调用,事务如何在这些方法之间传播。

   举个例子,方法 A 是一个事务的方法,方法 A 执行的时候调用了方法 B,此时方法 B 有无事务以及是否需要事务都会对方法 A 和方法 B 产生不同的影响,而这个影响是由两个方法的事务传播机制决定的。

   事务传播机制有 7 种类型,如下图:

 

   下面通过举例来说明各个类型的事务传播机制是如何发挥作用的。

   首先创建两个方法 A 和 B 实现数据的插入,插入数据A:

1 public class AService {
2     public void A(String name) {
3         userService.insertName("A-" + name);
4     }
5 
6 }

    插入数据B:

1 public class BService {
2     public void B(String name) {
3         userService.insertName("B-" + name);
4     }
5 
6 }

   使用伪代码创建 mainTest 方法和 childTest 方法

1    public void mainTest(String name)  {
2         // 存入a1
3         A(a1);
4         childTest(name);
5     }

    main调用test方法,其中

1    public void childTest(String name)  {
2         // 存入b1
3         B(b1);
4        throw new RuntimeException(); 
5        // 存入 b2
6        B2(b2);
7     }

   以上伪代码,调用 mainTest 方法,如果mainTest 和childTest 都不使用事务的话,数据存储的结果是如何呢?

   因为都没使用事务,所以 a1 和 b1 都存到成功了,而之后抛出异常之后,b2是不会执行的。所以 a1 和 b1 都插入的数据,而 b2 没有插入数据。

   一、支持当前事务

   1. REQUIRED(默认事务)

   如果当前方法没有事务,新建一个事务,如果已经存在一个事务中,则加入到这个事务中。

   示例1:根据场景举个例子,在 childTest 添加事务,设置传播属性为 REQUIRED,伪代码如下:

 1    public void mainTest(String name)  {
 2         // 存入a1
 3         A(a1);
 4         childTest(name);
 5     }
 6    @Transactional(propagation = Propagation.REQUIRED)
 7    public void childTest(String name)  {
 8         // 存入b1
 9         B(b1);
10        throw new RuntimeException(); 
11         // 存入b2
12         B2(b2)
13     }

   因为 mainTest 没有事务,而 childTest 又是新建一个事务,所以 a1 添加成功。在 childTest 因为抛出了异常,不会执行 b2 添加,而 b1 添加回滚。最终 a1 添加成功,b1没添加成功。

   2. SUPPORTS

   支持当前事务,如果当前没有事务,就以非事务方式执行。如果存在事务,就加入到当前事务。

   示例2:childTest 添加事务,传播属性设置为 SUPPORTS,伪代码如下:

1    public void mainTest(String name) {
2         A(a1);
3         childTest(name);
4     }
5    @Transactional(propagation = Propagation.SUPPORTS)
6    public void childTest(String name) {
7         B(b1);
8        throw new RuntimeException(); 
9     }

   传播属性为 SUPPORTS,如果没有事务,就以非事务的方式运行。表明两个方法都没有使用事务,没有事务的话,a1、b1 都添加成功

   示例3:mainTest 添加事务,设置传播属性为 REQUIRED。childTest 添加事务,设置传播属性为 SUPPORTS,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5     }
 6    @Transactional(propagation = Propagation.SUPPORTS)
 7    public void childTest(String name) {
 8         B(b1);
 9        throw new RuntimeException(); 
10        B2(b2);
11     }

   3. MANDATORY  如果存在事务,就加入到当前事务。如果不存在事务,就报错。

   这就说明如果想调用 MANDATORY 传播属性的方法,一定要有事务,不然就会报错。MANDATORY 类似功能限制,必须要被有事务的方法的调用,不然就会报错。

   示例4:首先在 childTest 添加事务,设置传播属性为 MANDATORY,伪代码如下:

 1    public void mainTest(String name) {
 2         A(a1);
 3         childTest(name);
 4     }
 5    @Transactional(propagation = Propagation.MANDATORY)
 6    public void childTest(String name) {
 7         B(b1);
 8        throw new RuntimeException(); 
 9        B2(b2);
10     }

   说明被标记为 mandatory 传播属性没找到事务,直接报错。因为 mainTest 没有事务,a1 添加成功。而 childTest 由于报错,b1 添加失败。

   在控制台直接报错:

   No existing transaction found for transaction marked with propagation 'mandatory'

   二、不支持当前事务

   1.  REQUIRES_NEW 创建一个新的事务。如果存在事务,就将事务挂起。

   无论是否有事务,都会创建一个新的事务

   示例5:childTest 添加事务,设置传播属性为 REQUIRES_NEW,伪代码如下:

1    public void mainTest(String name) {
2         A(a1);
3         childTest(name);
4     }
5    @Transactional(propagation = Propagation.REQUIRES_NEW)
6    public void childTest(String name) {
7         B(b1);
8        throw new RuntimeException(); 
9     }

   mainTest 不存在事务,a1 添加成功,childTest 新建了一个事务,报错,回滚 b1。所以 a1 添加成功,b1 添加失败。

   示例6:mainTest 添加事务,设置传播属性为 REQUIRED。childTest 添加事务,设置传播属性为 REQUIRES_NEW,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5     }
 6    @Transactional(propagation = Propagation.REQUIRES_NEW)
 7    public void childTest(String name) {
 8         B(b1);
 9        throw new RuntimeException(); 
10     }

   mainTest 创建了一个事务,childTest 新建一个事务,在 childTest 事务中,抛出异常,b1 回滚,异常抛到 mainTest 方法,a1 也回滚,最终 a1 和 b1 都回滚

   示例7:在示例2中,如果不想让 REQUIRES_NEW 传播属性影响到被调用事务,将异常捕获就不会影响到被调用事务。

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         try {
 5                childTest(name);
 6         } catch (Exception e) {
 7             e.printStackTrace();
 8         }   
 9     }
10    @Transactional(propagation = Propagation.REQUIRES_NEW)
11    public void childTest(String name) {
12         B(b1);
13        throw new RuntimeException(); 
14     }

   childTest 抛出了异常,在 mainTest 捕获了,对 mainTest 没有影响,所以 b1 被回滚,b1 添加失败,a1 添加成功。

   示例8:mainTest 设置传播属性为 REQUIRED,并在 mainTest 抛出异常。childTest 同样设置 REQUIRES_NEW 传播属性,伪代码如下: 

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5         throw new RuntimeException();    
 6     }
 7    @Transactional(propagation = Propagation.REQUIRES_NEW)
 8    public void childTest(String name) {
 9         B(b1);  
10         B2(b2);
11     }

   childTest 是一个新建的事务,只要不抛出异常是不会回滚,所以 b1 添加成功,而 mainTest 抛出了异常,a1 添加失败。

   2. NOT_SUPPORTED  无论是否存在当前事务,都是以非事务的方式运行。

   示例9:childTest 添加事务,设置传播属性为 NOT_SUPPORTED,伪代码如下:

 1    public void mainTest(String name) {
 2         A(a1);
 3         childTest(name);
 4 
 5     }
 6    @Transactional(propagation = Propagation.NOT_SUPPORTED)
 7    public void childTest(String name) {
 8         B(b1);  
 9        throw new RuntimeException();  
10     }

   NOT_SUPPORTED 都是以非事务的方式执行,childTest 不存在事务,b1 添加成功。而 mainTest 也是没有事务,a1 也添加成功。

   示例10:childTest 添加事务,设置传播属性为 NOT_SUPPORTED,mainTest 添加默认传播属性 REQUIRED,伪代码如下:

 1  @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5 
 6     }
 7    @Transactional(propagation = Propagation.NOT_SUPPORTED)
 8    public void childTest(String name) {
 9         B(b1);  
10        throw new RuntimeException();  
11    }

   其中 childTest 都是以非事务的方式执行,b1 添加成功。而 mainTest 存在事务,报错后回滚,a1 添加失败。

   3. NEVER  不使用事务,如果存在事务,就抛出异常。

   NEVER 的方法不使用事务,调用 NEVER 方法如果有事务,就抛出异常。

   示例11:childTest 添加 NEVER 传播属性,伪代码如下:

 1    public void mainTest(String name) {
 2         A(a1);
 3         childTest(name);
 4 
 5     }
 6    @Transactional(propagation = Propagation.NEVER)
 7    public void childTest(String name) {
 8         B(b1);  
 9        throw new RuntimeException();  
10        B2(b2);
11     }

   NEVER 不使用事务,mainTest 也不使用事务,所以 a1 和 b1 都添加成功,b2添加失败。

   示例12:mainTest 添加 REQUIRED 传播属性,childTest 传播属性设置为 NEVER,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5 
 6     }
 7    @Transactional(propagation = Propagation.NEVER)
 8    public void childTest(String name) {
 9         B(b1);  
10        throw new RuntimeException();  
11     }

   mainTest 存在事务,导致 childTest 报错,b1添加失败,childTest 抛错到 mainTest,a1 添加失败。

   三、嵌套事务 NESTED

   如果当前事务存在,就运行一个嵌套事务。如果不存在事务,就和 REQUIRED 一样新建一个事务。

   示例13:childTest 设置 NESTED 传播属性,伪代码如下:

 1    public void mainTest(String name) {
 2         A(a1);
 3         childTest(name);
 4 
 5     }
 6    @Transactional(propagation = Propagation.NESTED)
 7    public void childTest(String name) {
 8         B(b1);  
 9        throw new RuntimeException();  
10     }

    在 childTest 设置 NESTED 传播属性,相当于新建一个事务,所以 b1 添加失败, mainTest 没有事务,a1 添加成功。

   示例14:设置 mainTest 传播属性为 REQUIRED,新建一个事务,并在方法最后抛出异常。childTest 设置属性为 NESTED,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4         childTest(name);
 5         throw new RuntimeException();     
 6     }
 7    @Transactional(propagation = Propagation.NESTED)
 8    public void childTest(String name) {
 9         B(b1);         
10        B2(b2);
11     }

  childTest 是一个嵌套的事务,当主事务的抛出异常时,嵌套事务也受影响,即 a1、b1 和 b2 都添加失败。和 REQUIRES中的示例5不同的是,示例5不会影响 childTest 事务。

  NESTED 和 REQUIRES_NEW 的区别:

  REQUIRES_NEW 是开启一个新的事务,和调用的事务无关。调用方回滚,不会影响到 REQUIRED_NEW 事务。

  NESTED 是一个嵌套事务,是调用方的一个子事务,如果调用方事务回滚,NESTED 也会回滚。

  示例15:和示例2 一样,在 mainTest 设置传播属性为 REQUIRED,childTest 设置传播属性为 NESTED,不同的是,在 mainTest 捕获 childTest 抛出的异常,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4        try {
 5             childTest(name);
 6         } catch (RuntimeException e) {
 7             e.printStackTrace();
 8         } 
 9     }
10    @Transactional(propagation = Propagation.NESTED)
11    public void childTest(String name) {
12        B(b1);         
13        B2(b2);
14        throw new RuntimeException(); 
15     }

   childTest 是一个子事务,报错回滚,b1 和 b2 都添加失败。而 mainTest 捕获了异常,不受异常影响,a1 添加成功。

   示例16:mainTest 捕获 childTest 抛出的异常,伪代码如下:

 1    @Transactional(propagation = Propagation.REQUIRED)
 2    public void mainTest(String name) {
 3         A(a1);
 4        try {
 5             childTest(name);
 6         } catch (RuntimeException e) {
 7             e.printStackTrace();
 8         } 
 9     }
10    @Transactional(propagation = Propagation.REQUIRED)
11    public void childTest(String name) {
12        B(b1);         
13        B2(b2);
14        throw new RuntimeException(); 
15     }

   mainTest 和 childTest 两个方法都处于同一个事务,如果有一个方法报错回滚,并且被捕获。整个事务如果还有数据添加就会抛出 Transaction rolled back because it has been marked as rollback-only 异常,同一个事务,不能出现有的回滚了,有的不回滚,要么一起回滚,要不一起执行成功。所以全部数据都添加失败

  对比示例15和示例16,NESTED 和 REQUIRED 的区别:

  1)REQUIRED 传播属性表明调用方和被调用方都是使用同一个事务,被调用方出现异常,无论异常是否被捕获,因为属于同一个事务,只要发生异常,事务都会回滚。

  2)NESTED 被调用方出现异常,只要异常被捕获,只有被调用方事务回滚,调用方事务不受影响。

 

   参考链接:

   https://cloud.tencent.com/developer/article/2226182

posted @ 2024-10-11 11:48  欢乐豆123  阅读(158)  评论(0编辑  收藏  举报