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