- Spring事务传播机制 - 1.什么是事务传播机制?
- 2.Spring事务传播类型Propagation介绍
- 3.具体案例
 
- 总结
Spring事务传播机制
 
 
1.什么是事务传播机制?
 
 举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
 
 
简单说就是,我们方法调用通常是,一个方法调用另外一个,而不同方法可以有不同的事务,所以传播机制就是指在多个方法,事务要如何传播。
 
 
2.Spring事务传播类型Propagation介绍
 
一共有七种传播类型
 
- Propagation.REQUIRED
- Propagation.SUPPORTS
- Propagation.MANDATORY
- Propagation.REQUIRED_NEW
- Propagation.NOT_SUPPORTED
- Propagation.NESTED
- Propagation.NEVER
本文从案例结合解释一下不同传播类型下多个@Transactional方法会发生什么?在遇到异常情况下,不同传播机制会产生什么影响。
 
1. Propagation.REQUIRED
 
这是默认的传播机制,我们最常用的一种,也是@Transactional默认的一种
 
 如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 | // 示例1:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
简单来说就是,开启一个事务,上面的案例就是当main方法如果没开启事务,那么sub方法就会开启,如果main方法已经@Transactional开启了事务,sub方法就会加入外层方法的事务,所以上面方法执行在遇到异常时候会全部回滚
 
结果:
 
A、B、C全部无法插入。
 
| 1 2 3 4 5 6 7 8 9 10 11 | // 示例2:
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
结果:
 
A插入成功,BC开启新的事务,遇到异常回滚,B、C无法插入
 
2. Propagation.SUPPORTS
 
 当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
 
 
| 1 2 3 4 5 6 7 8 9 10 11 | // 示例3:
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.SUPPORTS)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
这个和REQUIRED很像,但是里层的sub方法事务取决于main方法,如果main方法有开启那么里面的就和外层事务一起,如果发生异常全部回滚。
 
结果:
 
A、B插入成功,C无法插入因为发生异常
 
3. Propagation.MANDATORY
 
 当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
 
 
| 1 2 3 4 5 6 7 8 9 10 11 | // 示例4:
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.MANDATORY)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
这种情形的执行结果就是insertA存储成功,而insertB和insertC没有存储。b和c没有存储,并不是事务回滚的原因,而是因为main方法没有声明事务,在去执行sub方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以sub方法里的内容就完全没有执行。
 
结果:
 
A插入成功,B、C无法插入,方法抛出异常
 
那么当main方法有事务的情况下
 
| 1 2 3 4 5 6 7 8 9 10 11 12 | // 示例5:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.MANDATORY)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
结果:
 
A、B、C全部无法插入,A、B回滚
 
4. Propagation.REQUIRED_NEW
 
 创建一个新事务,如果存在当前事务,则挂起该事务。
 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 | // 示例5:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
     throwRuntimeException;     //发生异常抛出
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.REQUIRES_NEW)
 publicvoidsub(){
     insertB();  //插入B
     insertC();  //调用C
 | 
 
因为sub方法会开启一个新的事务,所以main方法抛出的异常并不会影响sub方法的提交
 
结果:
 
A插入失败,B、C能插入成功
 
5. Propagation.NOT_SUPPORTED
 
 始终以非事务方式执行,如果当前存在事务,则挂起当前事务
 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 | // 示例6:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.NOT_SUPPORTED)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
示例6因为当main方法有事务的时候,就会挂起当前事务即main以事务运行,sub不以事务运行
 
所以最终结果:
 
A因为sub抛出异常事务回滚,插入失败,B因为不以事务运行插入成功,C因为遇到异常,后续不会执行,所以插入失败。
 
| 1 2 3 4 5 6 7 8 9 10 11 | // 示例7:
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.NOT_SUPPORTED)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
示例7这种情况就是所有方法都不会以事务运行,A、B均能插入成功,C无法插入
 
6. Propagation.NEVER
 
 不使用事务,如果当前事务存在,则抛出异常
 
 
| 1 2 3 4 5 6 7 8 9 10 11 | // 示例7:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.NEVER)
 publicvoidsub(){
     insertB();  //插入B
     insertC();  //调用C
 | 
 
sub因为是Never所以是不会执行直接抛出错误,所以main的事务遇到异常直接回滚,所以A回滚无法插入,B、C不会插入。
 
7. Propagation.NESTED
 
 如果当前事务存在,则在嵌套(父子)事务中执行,否则REQUIRED的操作一样(开启一个事务)
 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 | // 示例7:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     service.sub();   // 调用其他方法
     throwRuntimeException;     //发生异常抛出
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.NESTED)
 publicvoidsub(){
     insertB();  //插入B
     insertC();  //调用C
 | 
 
这个是最需要理解的一种传播机制,要理清楚嵌套(父子)事务,main的是父事务,sub是子事务,main发生异常全部都会回滚。
 
结果:
 
A、B、C全部回滚
 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 示例8:
 @Transactional(propagation = Propagation.REQUIRED)
 publicvoidmain(){
     insertA();  // 插入A 
     try{
          service.sub();   // 调用其他方法
     } catch(Exception e) {
     }
     insertD();
 }
 // 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
 @Transactional(propagation = Propagation.NESTED)
 publicvoidsub(){
     insertB();  //插入B
     throwRuntimeException;     //发生异常抛出
     insertC();  //调用C
 | 
 
示例8,子事务发生异常抛出,但父事务catch掉了,那么这个时候main方法就相当于正常执行没有发生异常,那么就只有子事务回滚。
 
结果:
 
A、D插入成功,B、C插入失败
 
- REQUIRED - 内外同一个事务,任何一个地方抛出异常全部一起回滚。
 
- REQUIRED_NEW - 内部开启一个新的事务,外部事务回滚并不会影响内部的事务,而如果内部事务抛出被catch也不会影响外部事务。
 
怎么样快速记忆,七个分四组,221这样记,两个一对互相类似
 
| 组 | 传播类型 | 含义 | 
|---|
| group1 | Propagation.REQUIRED | 如果当前已有事务则加入当前事务,否则开启新的事务 | 
| group1 | Propagation.REQUIRED_NEW | 无论当前是否有事务都开启新的事务 | 
| group2 | Propagation.SUPPORTED | 如果当前事务存在就加入事务,否则以非事务运行 | 
| group2 | Propagation.NOT_SUPPORTED | 始终以非事务方式执行,如果当前存在事务,则挂起当前事务 | 
| group3 | Propagation.NEVER | 不使用事务,如果当前事务存在,则抛出异常 | 
| group3 | Propagation.MANDATORY | 当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。 | 
| group4 | Propagation.NESTED | 父子(嵌套)事务,父回滚全回滚,子回滚不影响父事务 | 
 
 
3.具体案例
 
单纯讲案例比较枯燥,会觉得工作中什么情况会使用到呢,这边就举一个例子来讲解一下。
 
 在下单时候,我们最主要是写入订单、然后添加积分,最后记录日志
 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | @Service
   publicclassOrderServiceImpl implementsOrderService{
        @Transactional
        publicvoidplaceOrder(OrderDTO orderDTO){
            try{
                pointService.addPoint(Point point);
            } catch(Exception e) {
               // 记录错误信息
            }
            //省略...
        }
        //省略...
   }
 | 
 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | @Service
 publicclassPointServiceImpl implementsPointService{
      @Transactional(propagation = Propagation.NESTED)
      publicvoidaddPoint(Point point){
          try{
              recordService.addRecord(Record record);
          } catch(Exception e) {
             //省略...
          }
          //省略...
      }
      //省略...
 }
 | 
 
| 1 2 3 4 5 6 7 8 | @Service
 publicclassRecordServiceImpl implementsRecordService{
      @Transactional(propagation = Propagation.NOT_SUPPORTED)
      publicvoidaddRecord(Record record){
          //省略...
      }
      //省略...
 }
 | 
 
下单的操作不会影响添加积分的操作,所以我们使用NESTED,下单只要成功,添加积分可以成功或失败,失败的话就错误信息后续补偿。而记录日志我们可以有也可以没有,就可以设置为NOT_SUPPORTED不开启事务,使得事务的方法能尽可能的精简,避免一个很大的事务方法。
 
 
总结
 
本文讲解了Spring事务的七种传播机制,我们可以根据具体的类型,具体设置,避免事务的方法过于长,一个事务里面调用的库表越多,就越有可能造成死锁,所以我们要根据具体的需要拆分使用。