文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring @Transactional事务传播机制详解

2023-08-19 11:48

关注

我们日常工作中极少使用事务传播级别,单纯只是使用事务和rollbackfor抛出异常来解决事务问题,但其实我们很多时候使用的是不正确的,或者说会造成事务粒度过大,本文详解一下事务传播级别,也让自己更好地处理事务问题。

1. Spring事务传播机制

1.什么是事务传播机制?

举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

简单说就是,我们方法调用通常是,一个方法调用另外一个,而不同方法可以有不同的事务,所以传播机制就是指在多个方法,事务要如何传播。

2.Spring事务传播类型Propagation介绍

一共有七种传播类型

  1. Propagation.REQUIRED
  2. Propagation.SUPPORTS
  3. Propagation.MANDATORY
  4. Propagation.REQUIRED_NEW
  5. Propagation.NOT_SUPPORTED
  6. Propagation.NESTED
  7. Propagation.NEVER

本文从案例结合解释一下不同传播类型下多个@Transactional方法会发生什么?在遇到异常情况下,不同传播机制会产生什么影响。

1. Propagation.REQUIRED

这是默认的传播机制,我们最常用的一种,也是@Transactional默认的一种

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

// 示例1:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.REQUIRED)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

简单来说就是,开启一个事务,上面的案例就是当main方法如果没开启事务,那么sub方法就会开启,如果main方法已经@Transactional开启了事务,sub方法就会加入外层方法的事务,所以上面方法执行在遇到异常时候会全部回滚

结果:
A、B、C全部无法插入。

// 示例2:public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.REQUIRED)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

结果:
A插入成功,BC开启新的事务,遇到异常回滚,B、C无法插入

2. Propagation.SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

// 示例3:public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.SUPPORTS)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

这个和REQUIRED很像,但是里层的sub方法事务取决于main方法,如果main方法有开启那么里面的就和外层事务一起,如果发生异常全部回滚。
结果:
A、B插入成功,C无法插入因为发生异常

3. Propagation.MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

// 示例4:public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.MANDATORY)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

这种情形的执行结果就是insertA存储成功,而insertB和insertC没有存储。b和c没有存储,并不是事务回滚的原因,而是因为main方法没有声明事务,在去执行sub方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以sub方法里的内容就完全没有执行。

结果:
A插入成功,B、C无法插入,方法抛出异常

那么当main方法有事务的情况下

// 示例5:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.MANDATORY)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

结果:
A、B、C全部无法插入,A、B回滚

4. Propagation.REQUIRED_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。

// 示例5:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法    throw RuntimeException;     //发生异常抛出}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.REQUIRES_NEW)public void sub(){    insertB();  //插入B    insertC();  //调用C

因为sub方法会开启一个新的事务,所以main方法抛出的异常并不会影响sub方法的提交
结果:
A插入失败,B、C能插入成功

5. Propagation.NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务

// 示例6:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.NOT_SUPPORTED)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

示例6因为当main方法有事务的时候,就会挂起当前事务
即main以事务运行,sub不以事务运行
所以最终结果:
A因为sub抛出异常事务回滚,插入失败,B因为不以事务运行插入成功,C因为遇到异常,后续不会执行,所以插入失败。

// 示例7:public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.NOT_SUPPORTED)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

示例7这种情况就是所有方法都不会以事务运行,
A、B均能插入成功,C无法插入

6. Propagation.NEVER

不使用事务,如果当前事务存在,则抛出异常

// 示例7:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.NEVER)public void sub(){    insertB();  //插入B    insertC();  //调用C

sub因为是Never所以是不会执行直接抛出错误,所以main的事务遇到异常直接回滚,
所以A回滚无法插入,B、C不会插入。

7. Propagation.NESTED

如果当前事务存在,则在嵌套(父子)事务中执行,否则REQUIRED的操作一样(开启一个事务)

// 示例7:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     service.sub();   // 调用其他方法    throw RuntimeException;     //发生异常抛出}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.NESTED)public void sub(){    insertB();  //插入B    insertC();  //调用C

这个是最需要理解的一种传播机制,要理清楚嵌套(父子)事务,main的是父事务,sub是子事务,main发生异常全部都会回滚。
结果:
A、B、C全部回滚

// 示例8:@Transactional(propagation = Propagation.REQUIRED)public void main(){    insertA();  // 插入A     try {    service.sub();   // 调用其他方法} catch (Exception e) {}insertD();}// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用@Transactional(propagation = Propagation.NESTED)public void sub(){    insertB();  //插入B    throw RuntimeException;     //发生异常抛出    insertC();  //调用C

示例8,子事务发生异常抛出,但父事务catch掉了,那么这个时候main方法就相当于正常执行没有发生异常,那么就只有子事务回滚。
结果:
A、D插入成功,B、C插入失败

怎么样快速记忆,七个分四组,221这样记,两个一对互相类似

传播类型含义
group1Propagation.REQUIRED如果当前已有事务则加入当前事务,否则开启新的事务
group1Propagation.REQUIRED_NEW无论当前是否有事务都开启新的事务
group2Propagation.SUPPORTED如果当前事务存在就加入事务,否则以非事务运行
group2Propagation.NOT_SUPPORTED始终以非事务方式执行,如果当前存在事务,则挂起当前事务
group3Propagation.NEVER不使用事务,如果当前事务存在,则抛出异常
group3Propagation.MANDATORY当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
group4Propagation.NESTED父子(嵌套)事务,父回滚全回滚,子回滚不影响父事务

2.具体案例

单纯讲案例比较枯燥,会觉得工作中什么情况会使用到呢,这边就举一个例子来讲解一下。

在下单时候,我们最主要是写入订单、然后添加积分,最后记录日志

 @Service   public class OrderServiceImpl implements OrderService{                @Transactional        public void placeOrder(OrderDTO orderDTO){   try {                pointService.addPoint(Point point);            } catch (Exception e) {               // 记录错误信息            }            //省略...        }        //省略...   }
   @Service   public class PointServiceImpl implements PointService{                @Transactional(propagation = Propagation.NESTED)        public void addPoint(Point point){   try {                recordService.addRecord(Record record);            } catch (Exception e) {               //省略...            }            //省略...        }        //省略...   }
   @Service   public class RecordServiceImpl implements RecordService{                @Transactional(propagation = Propagation.NOT_SUPPORTED)        public void addRecord(Record record){              //省略...        }        //省略...   }

下单的操作不会影响添加积分的操作,所以我们使用NESTED,下单只要成功,添加积分可以成功或失败,失败的话就错误信息后续补偿。
而记录日志我们可以有也可以没有,就可以设置为NOT_SUPPORTED不开启事务,使得事务的方法能尽可能的精简,避免一个很大的事务方法。

总结

本文讲解了Spring事务的七种传播机制,我们可以根据具体的类型,具体设置,避免事务的方法过于长,一个事务里面调用的库表越多,就越有可能造成死锁,所以我们要根据具体的需要拆分使用。

来源地址:https://blog.csdn.net/qq_40922616/article/details/129919921

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯