添加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
修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。
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.NESTED
和Propagation.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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后
分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。
《Java高级面试》
《Java高级架构知识》
《算法知识》
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
待过,也去过华为、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)]