Spring系列第45篇:详解Spring事务中7种传播行为,赶紧收藏

添加2个方法,事务传播行为:REQUIRES_NEW,注意第2个方法内部最后一行会抛出一个异常。

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void requires_new(String name) {

this.jdbcTemplate.update(“insert into user2(name) VALUES (?)”, name);

}

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void requires_new_exception(String name) {

this.jdbcTemplate.update(“insert into user2(name) VALUES (?)”, name);

throw new RuntimeException();

}

场景1(2-1)

外围方法没有事务。

验证方法1
TxService添加

public void notransaction_exception_requiresNew_requiresNew(){

user1Service.requires_new(“张三”);

user2Service.requires_new(“李四”);

throw new RuntimeException();

}

Demo6Test中添加

@Test

public void notransaction_exception_requiresNew_requiresNew() {

txService.notransaction_exception_requiresNew_requiresNew();

}

运行输出

user1表数据:[{id=1, name=张三}]

user2表数据:[{id=1, name=李四}]

验证方法2
TxService添加

public void notransaction_requiresNew_requiresNew_exception(){

user1Service.requires_new(“张三”);

user2Service.requires_new_exception(“李四”);

}

测试用例,Demo6Test中添加

@Test

public void notransaction_requiresNew_requiresNew_exception() {

txService.notransaction_requiresNew_requiresNew_exception();

}

运行输出

user1表数据:[{id=1, name=张三}]

user2表数据:[]

结果分析

| 验证方法序号 | 数据库结果 | 结果分析 |

| — | — | — |

| 1 | “张三”插入,“李四”插入。 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。 |

| 2 | “张三”插入,“李四”未插入 | 外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常回滚,其他事务不受影响。 |

结论

通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景2(2-2)

外围方法开启事务。

验证方法1
TxService添加

@Transactional(propagation = Propagation.REQUIRED)

public void transaction_exception_required_requiresNew_requiresNew() {

user1Service.required(“张三”);

user2Service.requires_new(“李四”);

user2Service.requires_new(“王五”);

throw new RuntimeException();

}

测试用例,Demo6Test中添加

@Test

public void transaction_exception_required_requiresNew_requiresNew() {

txService.transaction_exception_required_requiresNew_requiresNew();

}

运行输出

user1表数据:[]

user2表数据:[{id=1, name=李四}, {id=2, name=王五}]

验证方法2
TxService添加

@Transactional(propagation = Propagation.REQUIRED)

public void transaction_required_requiresNew_requiresNew_exception() {

user1Service.required(“张三”);

user2Service.requires_new(“李四”);

user2Service.requires_new_exception(“王五”);

}

Demo6Test中添加

@Test

public void transaction_required_requiresNew_requiresNew_exception() {

txService.transaction_required_requiresNew_requiresNew_exception();

}

运行输出

user1表数据:[]

user2表数据:[{id=1, name=李四}]

验证方法3
TxService添加

@Transactional(propagation = Propagation.REQUIRED)

public void transaction_required_requiresNew_requiresNew_exception_try(){

user1Service.required(“张三”);

user2Service.requires_new(“李四”);

try {

user2Service.requires_new_exception(“王五”);

} catch (Exception e) {

System.out.println(“回滚”);

}

}

Demo6Test中添加

@Test

public void transaction_required_requiresNew_requiresNew_exception_try() {

txService.transaction_required_requiresNew_requiresNew_exception_try();

}

运行输出

回滚

user1表数据:[{id=1, name=张三}]

user2表数据:[{id=1, name=李四}]

结果分析

| 验证方法序号 | 数据库结果 | 结果分析 |

| — | — | — |

| 1 | “张三”未插入,“李四”插入,“王五”插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入“张三”的方法回滚。 |

| 2 | “张三”未插入,“李四”插入,“王五”未插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“张三”方法也被回滚。 |

| 3 | “张三”插入,“李四”插入,“王五”未插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。 |

结论

在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

3、PROPAGATION_NESTED


User1Service

添加1个方法,事务传播行为:NESTED

@Transactional(propagation = Propagation.NESTED)

public void nested(String name) {

this.jdbcTemplate.update(“insert into user1(name) VALUES (?)”, name);

}

User2Service

添加2个方法,事务传播行为:NESTED,注意第2个方法内部最后一行会抛出一个异常。

@Transactional(propagation = Propagation.NESTED)

public void nested(String name) {

this.jdbcTemplate.update(“insert into user2(name) VALUES (?)”, name);

}

@Transactional(propagation = Propagation.NESTED)

public void nested_exception(String name) {

this.jdbcTemplate.update(“insert into user2(name) VALUES (?)”, name);

throw new RuntimeException();

}

场景1(3-1)

外围方法没有事务。

验证方法1
TxService添加

public void notransaction_exception_nested_nested(){

user1Service.nested(“张三”);

user2Service.nested(“李四”);

throw new RuntimeException();

}

Demo6Test中添加

@Test

public void notransaction_exception_nested_nested() {

txService.notransaction_exception_nested_nested();

}

运行输出

user1表数据:[{id=1, name=张三}]

user2表数据:[{id=1, name=李四}]

验证方法2
TxService添加

public void notransaction_nested_nested_exception(){

user1Service.nested(“张三”);

user2Service.nested_exception(“李四”);

}

测试用例,Demo6Test中添加

@Test

public void notransaction_nested_nested_exception() {

txService.notransaction_nested_nested_exception();

}

运行输出

user1表数据:[{id=1, name=张三}]

user2表数据:[]

结果分析

| 验证方法序号 | 数据库结果 | 结果分析 |

| — | — | — |

| 1 | “张三”、“李四”均插入。 | 外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。 |

| 2 | “张三”插入,“李四”未插入。 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。 |

结论

通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景2(3-1)

外围方法开启事务。

验证方法1
TxService添加

@Transactional

public void transaction_exception_nested_nested(){

user1Service.nested(“张三”);

user2Service.nested(“李四”);

throw new RuntimeException();

}

测试用例,Demo6Test中添加

@Test

public void transaction_exception_nested_nested() {

txService.transaction_exception_nested_nested();

}

运行输出

user1表数据:[]

user2表数据:[]

验证方法2
TxService添加

@Transactional

public void transaction_nested_nested_exception(){

user1Service.nested(“张三”);

user2Service.nested_exception(“李四”);

}

Demo6Test中添加

@Test

public void transaction_nested_nested_exception() {

txService.transaction_nested_nested_exception();

}

运行输出

user1表数据:[]

user2表数据:[]

验证方法3
TxService添加

@Transactional

public void transaction_nested_nested_exception_try(){

user1Service.nested(“张三”);

try {

user2Service.nested_exception(“李四”);

} catch (Exception e) {

System.out.println(“方法回滚”);

}

}

Demo6Test中添加

@Test

public void transaction_nested_nested_exception_try() {

txService.transaction_nested_nested_exception_try();

}

运行输出

方法回滚

user1表数据:[{id=1, name=张三}]

user2表数据:[]

结果分析

| 验证方法序号 | 数据库结果 | 结果分析 |

| — | — | — |

| 1 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。 |

| 2 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。 |

| 3 | “张三”插入、“李四”未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。 |

结论

以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。

内部事务原理

以mysql为例,mysql中有个savepoint的功能,NESTED内部事务就是通过这个实现的。

REQUIRED,REQUIRES_NEW,NESTED比较


由“场景2(1-2)”和“场景2(3-2)”对比,我们可知:

REQUIRED和NESTED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。

由“场景2(2-2)”和“场景2(3-2)”对比,我们可知:

REQUIRES_NEW和NESTED都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

其他几个传播行为


REQUIRED,REQUIRES_NEW,NESTED这几个算是比较特殊的,比较常用的,剩下的5个传播行为,大家可以自己练练。

模拟用例


介绍了这么多事务传播行为,我们在实际工作中如何应用呢?下面我来举一个示例:

假设我们有一个注册的方法,方法中调用添加积分的方法,如果我们希望添加积分不会影响注册流程(即添加积分执行失败回滚不能使注册方法也回滚),我们会这样写:

@Service

public class UserServiceImpl implements UserService {

@Transactional

public void register(User user){

try {

membershipPointService.addPoint(Point point);

} catch (Exception e) {

//省略…

}

//省略…

}

//省略…

}

我们还规定注册失败要影响addPoint()方法(注册方法回滚添加积分方法也需要回滚),那么addPoint()方法就需要这样实现:

@Service

public class MembershipPointServiceImpl implements MembershipPointService{

@Transactional(propagation = Propagation.NESTED)

public void addPoint(Point point){

try {

recordService.addRecord(Record record);

} catch (Exception e) {

//省略…

}

//省略…

}

//省略…

}

我们注意到了在addPoint()中还调用了addRecord()方法,这个方法用来记录日志。他的实现如下:

@Service

public class RecordServiceImpl implements RecordService{

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public void addRecord(Record record){

//省略…

}

//省略…

}

我们注意到addRecord()方法中propagation = Propagation.NOT_SUPPORTED,因为对于日志无所谓精确,可以多一条也可以少一条,所以addRecord()方法本身和外围addPoint()方法抛出异常都不会使addRecord()方法回滚,并且addRecord()方法抛出异常也不会影响外围addPoint()方法的执行。

通过这个例子相信大家对事务传播行为的使用有了更加直观的认识,通过各种属性的组合确实能让我们的业务实现更加灵活多样。

总结


通过本文,相信大家对spring事务7种传播行为都有了深入的了解,希望对大家有所帮助,也欢迎大家留言交流!!!

案例源码


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。

《Java高级面试》

《Java高级架构知识》

《算法知识》

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-6eiRdWWL-1712768083088)]
[外链图片转存中…(img-RLGJUagx-1712768083089)]
[外链图片转存中…(img-vunGXQjZ-1712768083089)]
[外链图片转存中…(img-TOJN4eUb-1712768083089)]
[外链图片转存中…(img-m3VAgR44-1712768083090)]
[外链图片转存中…(img-zq4pz927-1712768083090)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-2x0AZUBZ-1712768083091)]

最后

分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。

《Java高级面试》

[外链图片转存中…(img-lEuwyyGf-1712768083091)]

《Java高级架构知识》

[外链图片转存中…(img-v0vH5bLs-1712768083091)]

《算法知识》

[外链图片转存中…(img-RwhQza97-1712768083092)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-AjB7hZJ6-1712768083092)]

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值