您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
spring七种事务传播行为
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
spring七种事务传播行为
自猿其说Tech
2021-06-24
IP归属:未知
91800浏览
计算机编程
> * 大家好,这里是公众号:**java小杰要加油** > * 我们都知道 **spring** 有七种事务传播行为,面试也经常被问道,不过他们长得都太像啦,老虎老鼠傻傻分不清楚,今天我们就用这篇文章来彻底搞懂他们! > * 文中有**大量代码**论证,建议**收藏在电脑端食用** * 话不多说,直接开车 ## spring的七种事务传播行为 > 以下事务传播属性都是打在B方法上的事务注解 * **Propagation.REQUIRED:** spring默认的事务传播行为,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务 * **Propagation.SUPPORTS:** A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行 * **Propagation.MANDATORY:** 只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常 * **Propagation.REQUIRES_NEW:** A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务 * **Propagation.NOT_SUPPORTED:** A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行 * **Propagation.NEVER:** 不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常 * **Propagation.NESTED:** 同 **Propagation.REQUIRED**,不过此传播属性还可以,**保存状态节点,从而避免所有嵌套事务都回滚** 我们看完了每个传播属性的一些解释,脑子里应该是还是蒙蒙的,下面来看下真正的代码 ## 实战 ### Propagation.REQUIRED * spring 默认的事务传播属性,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务 A接口 ``` java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用B接口的insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` 我们再来看下B接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` 假如说,我代码这么写的话,那么这个数据库里最终是会有什么数据呢? ``` java @PostMapping("/update") public Object updateTest(@RequestBody Test updateVO){ Integer flag = ATestService.updateTest(updateVO); return flag; } ``` 原数据库内容 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501192222.png) postman来一发看看 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501222702.png) 可以看到控制台的结果是这样的,他们共用一个事务(sqlSession是一样的) ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501222725.png) 此时数据库的内容也并没有发生变化,说明A,B接口都回滚了 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501214506.png) > 这个时候就会出现一个常见的面试题:如果B方法抛出的异常被**A方法try catch** 捕获了,那么A方法的操作还会回滚吗? 答案是:**会回滚** 来看下测试代码,我们在A方法中添加了捕获B方法抛出异常的代码 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); try { // 调用insertTest方法事务方法 BTestService.insertTest(test); }catch (Exception e){ System.out.println("A方法补获了异常"+e.getMessage()); } return i; } ``` 再次来一发postman,控制台输出测试结果如下 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501223151.png) 我们看数据库数据也没有变 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501223301.png) > 那么问题又来了,如果**A没有捕获,B方法自己捕获了异常**,那么事务还会回滚吗? 答案是:**不会** 把B接口的代码改一下 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = 0; try { i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; }catch (Exception e){ System.out.println("B方法补获了异常"+e.getMessage()); } return i; } ``` 同时把A方法的捕获异常去掉 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` 这个时候的结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501223951.png) 数据库的数据是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501224014.png) 由此可见,A和B两个接口都生效了都操作数据库了,都没有回滚 ##### A方法捕获和B方法捕获有什么区别吗(指捕获异常) * 区别就是,A方法捕获异常的话,*B方法的事务注解会感知到异常的发生,从而回滚*; * 而B方法自己捕获了,*那么B方法的事务注解就不会感知到异常了,所以不会回滚* > 只要理解了上面这个例子,我们以后各种*异常*/*传播属性*到底回滚不回滚就好分析啦! ### Propagation.SUPPORTS * A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行 我们把B接口的事务传播属性换成 Propagation.SUPPORTS ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` A方法 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` 测试结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501232733.png) 数据库的值也没有被改变 , 所以两个操作都被回滚了 那我们把A方法的事务注解去掉后再看一下 ```java @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` 测试结果是是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501233129.png) 数据库的值是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501233055.png) 由此可见,两个操作都没有被回滚,B方法是以非事务方式进行的操作 ### Propagation.MANDATORY > 只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常 A接口如下 ```java @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` B接口如下 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501234118.png) 数据库的值也没有变,由此可见,B方法的事务注解为 *Propagation.MANDATORY* 当A方法没事务时,则直接报错。 ### Propagation.REQUIRES_NEW * A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务 A方法 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` B方法 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` 结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210501234741.png) 其中可以发现 两个接口的 *DefaultSqlSession* 不一样,那么就表明,这两个不是一个事务,所以就是,当A接口存在事务的时候,B接口将其挂起并且重新开启一个新的事务 > * B方法抛出了异常,那么A方法没有捕获的话,则A,B方法都会回滚 > * A方法捕获了异常,则A方法不回滚 **还是那句话,如果在方法内捕获了异常,则此方法上的事务注解就感知不到这个异常的存在了,那么此方法的操作就不会回滚!** ### Propagation.NOT_SUPPORTED > A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行 A接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` B接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` 测试结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210502000854.png) 数据库的结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210502000918.png) > 我们可以看到,B接口生效了,确实插入了一条数据,A接口没有生效,没有更改数据,这是因为,异常在B接口内抛出来了,由于B接口的事务传播行为是 *Propagation.NOT_SUPPORTED* 则会挂起A接口的事务,B接口以非事务情况操作(所以报错也不回滚),异常刨到了A接口内,A接口是有事务的,则会回滚,所以就没有更改数据 ### Propagation.NEVER * 不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常 A接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); // 调用insertTest方法事务方法 BTestService.insertTest(test); return i; } ``` B接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` 结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210502002804.png) 数据库也没有被改变, 可见,当A接口有事务的情况下调用B接口,直接报错 ### Propagation.NESTED * 同 **Propagation.REQUIRED**,不过此传播属性还可以,**保存状态节点,从而避免所有嵌套事务都回滚** A接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) @Override public Integer updateTest(Test updateVO) { System.out.println("A updateTest方法"); int i = testMapper.updateByPrimaryKey(updateVO); Test test =new Test("小杰",24); try { // 调用insertTest方法事务方法 BTestService.insertTest(test); }catch (Exception e){ System.out.println("A方法补获了异常"+e.getMessage()); } return i; } ``` B接口 ```java @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED) @Override public Integer insertTest(Test updateVO) { System.out.println("B insertTest方法"); int i = testMapper.insert(updateVO); // 抛出异常 int a = 1 / 0 ; return i; } ``` 结果是 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210502005458.png) 数据库的变化如下 ![](https://oncexhj.oss-cn-beijing.aliyuncs.com/image_20210502005522.png) A接口的操作没有回滚,B操作的回滚了,这就是因为“savePoint”安全点,在进行B接口操作时,当前的状态(A接口已经操作完了)被保存至安全点,B接口失败的话,回滚只会回滚到这个安全点 > 注:需要在A接口里try catch B接口的异常 这里是公众号:***java小杰要加油***,我们下期见 原文链接:**https://mp.weixin.qq.com/s/7tShWEg0N9UPpWNhVSWOBQ** ![](//img1.jcloudcs.com/developer.jdcloud.com/ae366ac2-0c35-4d8d-a852-605ce6029cea20210624122137.png) ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:中台技术部-基础平台组 邢焕杰
原创文章,需联系作者,授权转载
上一篇:GraphQL实战(1)-GraphQL介绍
下一篇:架构研究:中台与前台的依赖方向(以2个中台api 设计为例)
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号