一个99%的人都说不清楚知识点-Spring 事务传播行为

面试过很多人,大部分都能把事务的四个特性及隔离级别说得七七八八,但当问到 Spring 的传播行为时,就基本上没人能说出个一二三了。

我们都知道,一个事务要么成功,要么失败。但当若干个事务配合完成一个复杂任务时,就不能简单的这样一刀切了。我们需要根据任务之间的亲疏关系来指定哪些任务需要联动回滚,哪些任务即使失败也不会影响其他任务。要解决这个问题,就需要了解事务的传播行为了。Spring 中有七种事务的传播行为,如下表所示:

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

Spring 可以通过 @Transactional 注解的 propagation 属性来设置不同的传播行为策略。Spring 为此提供了一个枚举类 Propagation,源码如下:

package org.springframework.transaction.annotation;import org.springframework.transaction.TransactionDefinition;public enum Propagation { /** * 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务, * 否则新建一个事务运行内部方法 */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * 支持事务,如果当前存在事务,就沿用当前事务, * 如果不存在,则继续采用无事务的方式运行内部方法 */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * 必须使用事务,如果当前没有事务,则会抛出异常, * 如果存在当前事务,则沿用当前事务 */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * 无论当前事务是否存在,都会创建新事务运行方法, * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立 */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * 不支持事务,当前存在事务时,将挂起事务,运行方法 */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行 */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * 在当前方法调用内部方法时,如果内部方法发生异常, * 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务 */ NESTED(TransactionDefinition.PROPAGATION_NESTED); ……}

接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。

REQUIRED

当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:

@Servicepublic class UserServiceImpl extends ServiceImpl implements UserService { @Autowired private UserMapper mapper; @Override @Transactional(propagation = Propagation.REQUIRED) public void addWithRequired(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void addWithRequiredAndException(User user) { mapper.insert(user); throw new RuntimeException(); }}

创建 TransactionServiceImpl 类,并添加如下方法:

@Slf4j@Servicepublic class TransactionServiceImpl implements TransactionService { @Autowired private UserService userService; @Override public void noTransaction_required_required_externalException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); userService.addWithRequired(xiaoJing); throw new RuntimeException(); } @Override public void noTransaction_required_requiredException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); userService.addWithRequiredAndException(xiaoJing); } @Override @Transactional public void transaction_required_required_externalException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); userService.addWithRequired(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_required_requiredException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); userService.addWithRequiredAndException(xiaoJing); } @Override @Transactional public void transaction_required_requiredException_try() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); try { userService.addWithRequiredAndException(xiaoJing); } catch (Exception e) { log.error(“发生异常,事务回滚!”); } }}

结果分析如下表所示:

方法

结果

分析

noTransaction_required_required_externalException

小水和小镜均成功入库

外部方法未开启事务,所以所有插入操作均未受到外部异常影响

noTransaction_required_requiredException

小水入库,小镜未入库

外部方法未开启事务,内部方法事务各自独立,互不影响,「小镜」的插入方法发生异常回滚,但「小水」的插入方法不受影响

transaction_required_required_externalException

小水和小镜均未入库

外部方法开启事务,所有内部方法均加入外部方法的事务中。而外部方法发生异常,所以导致所有操作都发生回滚

transaction_required_requiredException

小水和小镜均未入库

外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,所以导致所有操作都发生回滚

transaction_required_requiredException_try

小水和小镜均未入库

外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,即使发生异常的部分被 try-catch 住,所有操作仍然会回滚

前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。

PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。

REQUIRES_NEW

当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法:

@Servicepublic class UserServiceImpl extends ServiceImpl implements UserService { … @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addWithRequiredNew(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void addWithRequiredNewAndException(User user) { mapper.insert(user); throw new RuntimeException(); }}

在 TransactionServiceImpl 中添加如下方法:

@Slf4j@Servicepublic class TransactionServiceImpl implements TransactionService { … @Override @Transactional public void transaction_required_requiredNew_externalException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_required_requiredNew_requiredNewException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); User shuiJing = new User().setName(“水镜”); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); userService.addWithRequiredNewAndException(shuiJing); } @Override @Transactional public void transaction_required_requiredNewException_try() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); User shuiJing = new User().setName(“水镜”); userService.addWithRequired(xiaoShui); userService.addWithRequiredNew(xiaoJing); try { userService.addWithRequiredNewAndException(shuiJing); } catch (Exception e) { log.error(“发生异常,事务回滚!”); } }}

结果分析如下表所示:

方法

结果

分析

transaction_required_requiredNew_externalException

小水未入库,小镜入库

外部方法开启事务,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法开启一个独立的新事务,不受外部方法异常的影响

transaction_required_requiredNew_requiredNewException

小水未入库,小镜入库,水镜未入库

外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法也会开启一个独立的新事务,因此不会受到任何方法的影响,成功入库

transaction_required_requiredNewException_try

小水和小镜入库,水镜未入库

外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

NESTED

当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:

@Servicepublic class UserServiceImpl extends ServiceImpl implements UserService { … @Override @Transactional(propagation = Propagation.NESTED) public void addWithNested(User user) { mapper.insert(user); } @Override @Transactional(propagation = Propagation.NESTED) public void addWithNestedAndException(User user) { mapper.insert(user); throw new RuntimeException(); }}

在 TransactionServiceImpl 中添加如下方法:

@Slf4j@Servicepublic class TransactionServiceImpl implements TransactionService { … @Override @Transactional public void transaction_nested_nested_externalException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithNested(xiaoShui); userService.addWithNested(xiaoJing); throw new RuntimeException(); } @Override @Transactional public void transaction_nested_nestedException() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); userService.addWithNested(xiaoShui); userService.addWithNestedAndException(xiaoJing); } @Override @Transactional public void transaction_nested_nestedException_try() { User xiaoShui = new User().setName(“小水”); User xiaoJing = new User().setName(“小镜”); User shuiJing = new User().setName(“水镜”); userService.addWithRequired(xiaoShui); userService.addWithNested(xiaoJing); try { userService.addWithNestedAndException(shuiJing); } catch (Exception e) { log.error(“发生异常,事务回滚!”,e); } }}

结果分析如下表所示:

方法

结果

分析

transaction_nested_nested_externalException

小水和小镜均未入库

外部方法开启事务,内部方法开启各自的子事务,外部方法发生异常,主事务回滚,子事务跟随主事务回滚

transaction_nested_nestedException

小水和小镜均未入库

外部方法开启事务,内部方法开启各自的子事务,「小镜」的插入方法发生异常回滚自己的子事务;「小镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法在外部方法的子事务中,所以跟随主事务回滚

transaction_nested_nestedException_try

小水和小镜入库,水镜未入库

外部方法开启事务,「小镜」和「水镜」开启各自的子事务,「小水」加入外部方法的事务。「水镜」的插入方法发生异常回滚自己的子事务;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。

总结

REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。

REQUIRED 在没有外部事务的情况下,会开启一个独立的新事务,且不会对其他同级事务造成影响;而当存在外部事务的情况下,则会与外部事务同生共死。

NESTED 在没有外部事务的情况下与 REQUIRED 效果相同;而当存在外部事务的情况下,当外部事务回滚时,它会创建一个嵌套事务(子事务)。外部事务回滚时,子事务会跟着回滚;但子事务的回滚不会对外部事务和其他同级事务造成影响。

更多独家精彩内容尽在我的新书 《Spring Boot趣味实战课(博文视点出品)》(刘水镜)【摘要 书评 试读】- 京东图书 中。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
用户投稿
上一篇 2022年6月28日 22:13
下一篇 2022年6月28日 22:13

相关推荐

  • 分享4条发微商朋友圈的方法(微商朋友圈应该怎么发)

    对于微商朋友来说,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底该怎么发呢? 为什么同样是经营一个朋友圈,有的微商看起来逼格满满,实际效果也不错;而有的却动都不动就被屏蔽甚至拉黑…

    2022年11月27日
  • 《宝可梦朱紫》梦特性怎么获得?隐藏特性获取方法推荐

    宝可梦朱紫里有很多宝可梦都是拥有梦特性会变强的宝可梦,很多玩家不知道梦特性怎么获得,下面就给大家带来宝可梦朱紫隐藏特性获取方法推荐,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 …

    2022年11月25日
  • 《宝可梦朱紫》奇鲁莉安怎么进化?奇鲁莉安进化方法分享

    宝可梦朱紫中的奇鲁莉安要怎么进化呢?很多玩家都不知道,下面就给大家带来宝可梦朱紫奇鲁莉安进化方法分享,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 奇鲁莉安进化方法分享 奇鲁莉安…

    2022年11月25日
  • 北京疫情为什么突然爆发(北京疫情为什么突然增多)

    这几天北京疫情的新增情况也突然开始变得严重起来了,大家对本轮北京疫情爆发的原因也十分好奇。北京疫情为什么突然爆发?尤其是本周北京疫情的新增开始越来越多了,这让民众对于本轮疫情的发展…

    2022年11月25日
  • 为什么北京疫情越来越严重(疫情为什么越控制越严重)

    有在关注北京疫情的小伙伴应该注意到了,这几天北京疫情的新增数据情况也开始越发的严重起来,大家对北京此次疫情发展情况也都不断分析。为什么北京疫情越来越严重?导致北京本轮疫情的原因是什…

    2022年11月25日
  • 2023年摆地摊卖什么最赚钱而且很受欢迎(2022年摆地摊合法吗)

    关于摆地摊赚钱每年的热度也是非常高的,很多想要做点小本生意的商家也都会选择摆地摊这个项目,所需要的成本也是非常低的。那么,2023年摆地摊卖什么最赚钱而且很受欢迎?今天极客号小编整…

    2022年11月25日
  • 密接5+3是什么意思(密接人员是什么意思)

    如今新冠病例的传播速度是越来越快了,对于感染了新冠病毒的人员来说都会采取隔离观看措施。而据了解,当前国内又对新冠疫情防控政策做了新的调整优化,其中密接管理调整为“5+3”。很多人对…

    2022年11月25日
  • 不知道考研真题哪里找?看这里!

    考研备考冲刺阶段,知道大家都很疲惫,但是,别让自己之前的努力白费,再坚持坚持,会看到你想要的结果的。 要是你想考研成功,有一个资料就必须要有。它就是:历年考研真题。 考研真题的重要…

    2022年11月25日
  • 淘宝直播平台抽成多少(淘宝直播平台抽成比例)

    随着时代的发展,现在直播带货已经成为主要带货方式,其中淘宝是主流带货平台,不少人在上面直播带货赚钱,一些小伙伴也想加入,那么淘宝直播平台抽成多少?下面小编为大家带来淘宝直播平台抽成…

    2022年11月24日
  • 淘宝直播开通后带货链接怎么做(淘宝直播需要开通淘宝店铺吗)

    直播带货无论是对于商家来说还是主播收益都是非常可观的,所以不少平台都有直播带货功能,一些小伙伴也想加入淘宝直播,那么淘宝直播开通后带货链接怎么做?下面小编为大家带来淘宝直播开通后带…

    2022年11月24日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息