-
2021-01-05 16:39:08
事务传播行为
什么叫事务传播行为?听起来挺高端的,其实很简单。
即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。Spring定义了七种传播行为:
1、PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
可以把事务想像成一个胶囊,在这个场景下方法B用的是方法A产生的胶囊(事务)。举例有两个方法:
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { // do something }
单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。2、PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
举例有两个方法:@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事务属性为SUPPORTS @Transactional(propagation = Propagation.SUPPORTS) public void methodB() { // do something }
单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
3、PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事务属性为MANDATORY @Transactional(propagation = Propagation.MANDATORY) public void methodB() { // do something }
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
4、PROPAGATION_REQUIRES_NEW
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。
它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。@Transactional(propagation = Propagation.REQUIRED) public void methodA() { doSomeThingA(); methodB(); doSomeThingB(); // do something else } // 事务属性为REQUIRES_NEW @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // do something }
当调用
main{ methodA(); }
相当于调用
main(){ TransactionManager tm = null; try{ //获得一个JTA事务管理器 tm = getTransactionManager(); tm.begin();//开启一个新的事务 Transaction ts1 = tm.getTransaction(); doSomeThing(); tm.suspend();//挂起当前事务 try{ tm.begin();//重新开启第二个事务 Transaction ts2 = tm.getTransaction(); methodB(); ts2.commit();//提交第二个事务 } Catch(RunTimeException ex) { ts2.rollback();//回滚第二个事务 } finally { //释放资源 } //methodB执行完后,恢复第一个事务 tm.resume(ts1); doSomeThingB(); ts1.commit();//提交第一个事务 } catch(RunTimeException ex) { ts1.rollback();//回滚第一个事务 } finally { //释放资源 } }
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了。
5、PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
6、PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。
7、PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。 需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。这里关键是嵌套执行。
@Transactional(propagation = Propagation.REQUIRED) methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } @Transactional(propagation = Propagation.NEWSTED) methodB(){ …… }
如果单独调用methodB方法,则按REQUIRED属性执行。
如果调用methodA方法,相当于下面的效果:
main(){ Connection con = null; Savepoint savepoint = null; try{ con = getConnection(); con.setAutoCommit(false); doSomeThingA(); savepoint = con2.setSavepoint(); try{ methodB(); } catch(RuntimeException ex) { con.rollback(savepoint); } finally { //释放资源 } doSomeThingB(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { //释放资源 } }
当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。
但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
- 使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
- 使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
更多相关内容 -
Spring中事务传播行为的介绍
2020-08-26 12:08:22今天小编就为大家分享一篇关于Spring中事务传播行为的介绍,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧 -
Spring中的事务传播行为示例详解
2020-08-25 18:32:02主要给大家介绍了关于Spring中事务传播行为的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
互联网传播行为的时序演化与预测
2021-01-14 06:01:14互联网的传播行为对研究网络拓扑结构和动态行为的关系具有重要作用。选取CAIDA_Ark项目下不同地区4个监测点的有效路径样本数据,统计网络访问时间与访问直径,发现它们的相关性极弱,网络访问时间呈多峰重尾分布。... -
复杂网络上SIRS类疾病传播行为分析_网络SIR_Ba——net_SIRS_SIR传播模型_sir_
2021-09-30 15:05:17Ba 网络模型 SIR 模型的传播过程懂的都懂 -
详解事务的7种传播行为
2021-04-11 16:52:36什么是Spring事务的传播行为?事务传播行为的七种类型有哪些?将异常try-catch捕获,事务是否还会回滚?文章目录
一、什么是Spring事务的传播行为?
事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式
比如XML中配置:XML:<tx:method name="..." propagation="REQUIRED"/>
注解配置:@Transactional(propagation=Propagation.REQUIRED)
二、事务传播行为的七种类型
三、Propagation.REQUIRED(默认)
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
例子如下,后续各种情况将此例子展开讲解。
...... // ===========测试类 @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } // @Transactional(propagation = Propagation.REQUIRED) public void saveChildren() { saveChild1(); int a = 1 / 0; saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
结果是除以0错误
java.lang.ArithmeticException: / by zero
这个例子中,有一个子方法
saveParent()
是没写事务注解的,用于对照比较有事务的子方法saveChildren()
情况一:
父方法testPropagationTrans()
开启事务,子方法saveChildren()
没有开启事务。子方法
saveChildren()
和saveParent()
同处在父方法的事务中,saveChildren()
除以0异常导致事务中对数据库的操作都回滚。所以没有记录插入。
情况二:
父方法testPropagationTrans()
不开启事务,只有saveChildren()
开启事务...... // ==========测试类 // @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } @Transactional(propagation = Propagation.REQUIRED) public void saveChildren() { saveChild1(); int a = 1 / 0; saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
只有saveChildren()
开启事务,saveChildren()
发生异常就回滚该方法里面的数据库操作,所以数据库插入一条parent
记录还是可以成功的。情况三:
父方法testPropagationTrans()
和子方法saveChildren()
均开启事务。这里不拉出多余代码,文字解释+结果图即可。
saveChildren()
发生异常,开始回滚,异常继续往上抛,父方法testPropagationTrans()
也知道发生了异常,父方法里面所有数据库操作都回滚,所以saveParent()
数据库插入的parent
记录也回滚,最后数据库没有数据插入。
疑问:异常继续往上抛父方法才知道发生了异常,导致方法里所有数据库操作回滚,那么我把这个异常try-catch
,是不是就只有saveChildren()
回滚呢?那可不一定,跟着我继续来看。
四、将异常try-catch捕获,事务是否还会回滚?(这个总结很重要)
所有的讲解都是围绕开头第一段代码,依然是情况一二三,只不过此时多了
try-catch
...... public void testPropagationTrans() { saveParent(); try { saveChildren(); } catch (Exception e) { e.printStackTrace(); } } ......
情况一:
父方法testPropagationTrans()
开启事务,子方法saveChildren()
没有开启事务。
saveChildren()
产生的异常被捕获,没有继续上抛,父方法开启的事务不会回滚,故插入2条数据。情况二:
父方法testPropagationTrans()
不开启事务,只有saveChildren()
开启事务.
saveChildren()
发生异常,回滚数据,parent
记录插入不受影响。情况三:
父方法testPropagationTrans()
和子方法saveChildren()
均开启事务。
结果发现即使saveChildren()
产生的异常被try-catch
,父事务也回滚。综上:
1. 父方法和子方法都开启事务,异常发生让子事务回滚,父事务一定回滚(子事务没将父事务挂起的情况下),不管是否被try-catch
包裹,第四节的情况三就是最好的例子。
2. 只要try-catch
在内层,@Transactional
在外层,异常被try-catch
住,事务就不会回滚。
3. 但是如果@Transactional
在内层,try-catch
在外层,那try-catch
还没来得及处理异常就在@Transactional
注解作用下回滚了,第四节的情况二就是最好的例子。五、Propagation.SUPPORTS
如果当前有事务,则使用事务,如果当前没有事务,就以非事务方式执行
情况一:
父方法testPropagationTrans()
不开启事务,子方法saveChildren()
事务传播类型改为Propagation.SUPPORTS
外层父方法没有事务,子方法saveChildren()
也就以非事务方式执行,这里不会回滚,所以有2条数据。情况二:
父方法testPropagationTrans()
开启事务,传播类型为Propagation.REQUIRED
,子方法saveChildren()
事务传播类型改为Propagation.SUPPORTS
子方法saveChildren()
当前父方法开启了事务,故使用事务,saveChildren()
发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。六、Propagation.MANDATORY
支持当前的事务,如果当前没有事务,就抛出异常。
情况一:
父方法testPropagationTrans()
不开启事务,子方法saveChildren()
事务传播类型改为Propagation.MANDATORY
此时saveChildren()
直接抛出异常,和之前的除以0异常不同,No existing transaction found for transaction marked with propagation 'mandatory'
,外层没有事务,就会抛异常。
异常导致saveChildren()
方法没执行,没有child-1
记录插入,但是数据库会插入parent
记录,因为父方法没开启事务,不影响saveParent()
的执行。情况二:
父方法testPropagationTrans()
开启事务,传播类型为Propagation.REQUIRED
,子方法saveChildren()
事务传播类型改为Propagation.MANDATORY
子方法saveChildren()
支持父事务,故使用事务,saveChildren()
发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。七、Propagation.REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
这里说得多一些,所以代码整个给出来
...... // ==========测试类 @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveChildren() { saveChild1(); int a = 1 / 0; saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
有人会疑问了,saveChildren() 都新建事务将原事务挂起了,为什么子事务回滚父事务也会回滚?原来的事务都挂起了,子事务回滚和父事务回滚没有必然联系了。 其实这里原因是因为异常抛给了父事务,导致回滚。我们可以将
saveChildren()
用try-catch
包裹,就会发现,testPropagationTrans()
所在的事务并没有回滚,因为parent
记录插入成功了。
为了不混淆,我们将异常提出来...... // ==========测试类 @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); int a = 1 / 0; // =======将saveChildren的异常提到外面============= } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveChildren() { saveChild1(); saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
两条child
记录插入成功,而parent
记录没插入,说明子事务执行完就commit
了,父事务所有相关数据库的操作全部回滚,parent
记录的插入被撤销,但这也影响不了已经commit
的子事务。举个形象的例子,小区的人都用小区网,我觉得小区网太慢了,自己拉了一根光纤,某天因施工小区网断掉了,大家都受到影响,但是我自己的网不受影响。
八、Propagation.NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
...... // ==========测试类 @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void saveChildren() { saveChild1(); int a = 1 / 0; // ===============这里有异常=========== saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
结果是child-1
记录插入成功,parent
没有插入成功。原因是saveChildren()
以非事务方式执行,并将父事务挂起,执行之后发生异常,但是child-1
插入成功,异常抛到父事务后数据库操作全部回滚,所以parent
没有插入成功。这种情况主要用在查询操作,比如在类上开启了事务,类里面的所有方法都开启了事务,插入删除更新是需要的,但是查询就没必要了,所以可以用这个
Propagation.NOT_SUPPORTED
将查询方法以非事务的方式执行。九、Propagation.NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。与
Propagation.MANDATORY
正好相反。...... // ==========测试类 @Transactional(propagation = Propagation.REQUIRED) public void testPropagationTrans() { saveParent(); saveChildren(); } ...... // ===========service实现类 public void saveParent() { Stu stu = new Stu(); stu.setName("parent"); stu.setAge(19); stuMapper.insert(stu); // 数据库插入一条parent记录 } @Transactional(propagation = Propagation.NEVER) public void saveChildren() { saveChild1(); int a = 1 / 0; // ===============这里有异常=========== saveChild2(); } public void saveChild1() { Stu stu1 = new Stu(); stu1.setName("child-1"); stu1.setAge(11); stuMapper.insert(stu1); // 数据库插入一条child-1记录 } public void saveChild2() { Stu stu2 = new Stu(); stu2.setName("child-2"); stu2.setAge(22); stuMapper.insert(stu2); // 数据库插入一条child-2记录 }
结果异常
Existing transaction found for transaction marked with propagation 'never'
数据库也没有记录,因为parent
记录插入后,收到saveChildren()
的异常导致父事务回滚,而saveChildren()
因为注解检查到异常,内容就没执行。如果去掉testPropagationTrans()
事务,那么执行如下,方法都是以非事务方式执行。
十、Propagation.NESTED
如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚,如果当前没有事务,就新建事务运行。
运行结果和原因与
Propagation.REQUIRED
一模一样。几乎没区别,这种情况用得少。
欢迎一键三连~
有问题请留言,大家一起探讨学习
----------------------Talk is cheap, show me the code----------------------- -
Spring中的事务传播行为
2021-10-17 17:15:13其中,在声明式事务时,有一个事务的属性为propagation,即事务的传播行为。今天,就来讨论该属性的作用。 什么是事务的传播行为? 首先,事务的传播行为,可以拆成两...前言:在Spring中,我们可以通过声明式事务,实现对数据库操作的事务管理。其中,在声明式事务时,有一个事务的属性为propagation,即事务的传播行为。今天,就来讨论该属性的作用。
什么是事务的传播行为?
首先,事务的传播行为,可以拆成两部分理解,即事务的传播,和事务的行为。指的是,当有两个或以上的方法同时声明为事务方法(事务方法:即加了事务管理的增删改方法)时,如果在一次程序执行过程中,这些事务方法彼此间相互调用,那么这些事务方法的事务,应该如何来进行管理?
其中,事务的传播,指的是嵌套调用的多个事务方法,是否会共享同一个事务,即调用者所处的事务是否会传播给被调用者(前提:两者都是属于事务方法)。
而事务的行为,主要指的就是事务的提交或者回滚。OK,那么解析完事务的传播行为的概念,接下来再说一个,关于事务,核心关键点只有一个,那就是,一个事务,就是一条数据库连接。而刚刚提到的事务的传播,即为调用者和被调用者共享同一个事务,也就是共用同一个数据库连接。
所以,事务的传播行为,通俗点理解,就是:
传播行为,就是设置当前这个事务方法(被调用者),是不是和调用者所在的大事务(外层事务),共享同一个事务(即是用同一条连接)。
事务的传播行为有哪些?
说完事务的传播行为的概念,接下来,我们对它所能设置的值进行解析,即事务有哪些传播行为。
Spring中定义了七种事务的传播行为,完整版的传播行为如下:
其中,REQUIRED(事务传播行为的默认值)、REQUIRED_NEW这两个是最重要也是最常用的事务传播行为。所以接下来,我们就针对这两个进行举例说明。
两种事务传播行为的举例说明
用最经典的银行转账例子,来进行这一次的举例。
首先,准备两张表,分别用来表示账户持有人、账户余额:
# 账户持有人 CREATE TABLE t_account( id INT PRIMARY KEY AUTO_INCREMENT, t_name VARCHAR(20) ); # 账户余额 CREATE TABLE t_balance( t_id INT, t_money DECIMAL, FOREIGN KEY(t_id) REFERENCES t_account(id) );
其中的账户余额表,就是我们这一次要操作的对象。
接着,在java层面,分别创建BalanceDao及其实现类、BalanceService业务类,用来操作t_balance表中的数据。
// Dao接口: 为了实验效果明显,将账户余额增加钱和减少钱两个操作分成两个方法: public interface BalanaceDao { //增加余额 int addBalance(Integer id, BigDecimal money); //减少余额 int reduceBalance(Integer id, BigDecimal money); }
// Dao实现类: @Repository public class BalanceDaoImpl implements BalanaceDao { @Autowired private JdbcTemplate jdbcTemplate; @Override @Transactional(propagation = Propagation.REQUIRED) public int addBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money + ? where t_id = ? ;"; //模拟异常: // int i = 1 / 0; return jdbcTemplate.update(sql, money, id); } @Override @Transactional(propagation = Propagation.REQUIRED) public int reduceBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money - ? where t_id = ? ;"; //模拟异常: // int i = 1 / 0; return jdbcTemplate.update(sql, money, id); } }
// Service层: @Service public class BalanceService { @Autowired private BalanaceDao balanaceDao; //转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //模拟异常: // int i = 1/0; //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); //模拟异常: // int j = 1/0; } }
对Dao层和Service层进行说明:首先,这一次是通过Service层的transfer() 来调用Dao层的 reduceBalance() 和 addBalance() 进行实验。 这三个方法都声明为事务方法,都加了@Transactional 注解。
所以事务的传播行为,指的就是:当 transfer() 为事务方法,已经开启了自己的事务时(拥有一条数据库连接),如果调用了另外的两个事务方法reduceBalance() 和 addBalance() , 这两个事务方法是会独立开启事务(获取属于自己的一条独立数据库连接), 还是和调用者即 transfer() 共享同一个事务(和调用者共用同一条数据库连接 )。
先来第一种事务传播行为:REQUIRED
REQUIRED 是Spring中事务的默认传播行为,当方法被声明为事务方法,且传播行为设置为 REQUIRED 时,即表示 这个方法必须运行在事务中,但是否是自己的独立事务并无关紧要。
类比一个不恰当的例子,老王约我去旅游,我必须是坐车去旅游。 当 我的事务为 REQUIRED 时,表示 如果老王开了车,那我就坐老王的车;如果老王没开车,那我就自己开车去。
那么这种情况下,这个设置为REQUIRED的事务方法,就和调用者同舟共济,要么一起提交,要么一起回滚。
开始实验:
初始时,表中数据如下:
测试类:@Test public void test(){ // 创建SpringIOC容器 ApplicationContext app = new ClassPathXmlApplicationContext("TxXml.xml"); BalanceService balanceService = app.getBean("balanceService", BalanceService.class); //测试转账业务 balanceService.transfer(); }
实验一:调用者的事务出现了异常,被调用者正常运行
- 首先,在Service中开启模拟异常
@Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); //模拟异常: int j = 1/0; }
- Dao层的两个方法保持不变,两个方法的事务传播行为属性都设置为REQUIRED (默认值) 。
- 运行结果如下:
IDEA报算术异常
而数据库中的数据并未更改
说明:在Service的transfer() 中,虽然两个事务方法都成功执行了,但由于最后出现了运行时异常,所以导致transfer()的事务发生了回滚。而Dao层的两个事务方法由于传播行为设置为REQUIRED,所以和transfer() 的事务同舟共济,就一起被回滚了,因此数据库中的数据并未发生更改。
实验二:调用者正常运行,但被调用者中出现了异常
- Service中关闭模拟异常,而在Dao中的addBalance() 开启模拟异常
//转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); }
@Override @Transactional(propagation = Propagation.REQUIRED) public int addBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money + ? where t_id = ? ;"; //模拟异常: int i = 1 / 0; return jdbcTemplate.update(sql, money, id); }
- 运行,查看结果:
IDEA中仍然报错:
数据库中的表仍然没有发生更改:
说明:虽然Service中的transfer() 并未发生异常,reduceBalance() 也正常运行,但由于addBalance() 中出现异常,而异常没有被处理,会向上传导,所以异常会传导到调用者处,也就是transfer()中,所以它的事务会进行回滚。而这三个事务方法共享同一个事务,因此,最终reduceBalance() 的操作并未生效。
实验三:设调用者为非事务方法,且调用者的方法最后出现异常,两个被调用方法仍然是事务方法,且无异常:
- Service层代码:
//转账业务:用户1给用户2转账100元 // @Transactional 取消事务注解 public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); //模拟异常: int j = 1/0; }
- Dao层的代码无异常,且都为事务方法
- 查看运行结果,如下:
IDEA仍报异常:
但与前两个实验不同的是,数据库中的数据发生了更改。
说明:由于Service层的transfer()不是事务方法,因此运行时并未开启事务,但调用第一个Dao层方法即reduceBalance()时,reduceBalance()是事务方法,所以会自己开启一个事务,顺利执行完后就提交然后关闭该事务;然后接着调用addBalance(),它也是个事务方法,由于调用者自身没有开启事务,所以addBalance()也会自己开启一个事务,执行完自己的数据库操作后,提交事务然后关闭。最后,当程序的调用权又回到了transfer()中时,虽然最后出现了运行时异常,但由于前面的两个Dao层的方法已经被成功调用并且各自开启事务提交了修改数据,因此最后出现的异常并没有影响到数据库的修改。
总结:
当事务的属性 propagation 为 REQUIRED时,该事务方法必须运行在事务中,若调用者(调用了该事务方法的方法)本身已开启了事务(即已经获取了数据库的连接),那么该事务方法会获取到调用者的事务(即同一条数据库连接),并在该事务中执行自己的数据库操作;若调用者自身不是事务方法,没有开启事务,那么该事务方法会自己获取一条数据库连接以开启事务,并在执行完自己的数据库操作后关闭该连接。
总而言之,REQUIRED 的传播行为,表示了这嵌套调用的多个事务方法,是共用同一个事务,同舟共济,要么一起提交,要么一起回滚。
画个示意图:
再说第二种事务传播行为:REQUIRES_NEW
REQUIRES_NEW 的事务传播行为,是指当前事务方法,必须是自己新开一个事务执行数据库操作(即获取一条新的数据库连接),不与调用者共用同一个事务,即使调用者本身就处在一个事务中。如果调用者本身就处于一个事务中,那么当前事务方法执行时,调用者所在的事务将被挂起,而等当前事务方法执行完毕,将自己的事务关闭后,调用者的事务才得以继续往下执行。
还是举个不恰当的例子:隔离老王约我(设置为REQUIRES_NEW的事务方法)去旅游,到目的地的方式有多种,其中一种方式是自己开车(相当于自己开启了事务),那么无论老王是否有开车(无论调用者是否有开启自己的事务),我都要自己开车(设置为REQUIRES_NEW的事务方法都要开启自己的事务)。
这种情况下,设置为REQUIRES_NEW的事务方法会和调用者的事务分道扬镳。
开始实验:
表中的数据还原:
仍然采用上面的Service层和Dao层:@Service public class BalanceService { @Autowired private BalanaceDao balanaceDao; //转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //模拟异常: // int i = 1/0; //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); //模拟异常: // int j = 1/0; } }
@Repository public class BalanceDaoImpl implements BalanaceDao { @Autowired private JdbcTemplate jdbcTemplate; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public int addBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money + ? where t_id = ? ;"; //模拟异常: // int i = 1 / 0; return jdbcTemplate.update(sql, money, id); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public int reduceBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money - ? where t_id = ? ;"; //模拟异常: // int i = 1 / 0; return jdbcTemplate.update(sql, money, id); } }
实验一:两个被调用的事务方法正常执行,调用者出现异常:
- 修改Service层的transfer(),模拟异常:
//转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //模拟异常: int i = 1/0; //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); }
-
Dao层的两个方法,保持不变
-
执行,查看数据库结果:
IDEA报算术异常:
数据库中的数据如下:
可以发现,数据库中的数据发生了更新。说明:因为Dao层的两个事务方法的事务传播行为都是REQUIRES_NEW,也就是说,虽然调用者transfer() 本身也是个事务方法,开启了自己的事务。但当调用到Dao层的两个方法时,由于传播行为,所以两个Dao层的方法都会开启自己的事务,不会和transfer()公用同一个事务。因此,当调用reduceBalance()时,transfer() 的事务被挂起(可以理解为线程阻塞,即暂停了),而reduceBalance()开启了个独立的新事务,执行自己的数据库操作,最后顺序执行完毕,提交了事务然后结束调用,transfer()的事务得以继续往下执行,往下执行后,遇到 算术异常,于是事务transfer()所在的事务回滚,程序终止。
特别注意,此时虽然transfer()的事务发生了回滚,但由于reduceBalance()是独立新开一个事务,并且顺利完成后提交了事务,因此transfer()的事务回滚并不影响reduceBalance() 的数据库操作结果。这就是所谓的分道扬镳。
实验二:两个被调用的事务方法正常执行,改变调用者出现异常的位置:
仍然是对数据库中的数据进行还原,然后开始操作:
- 改变Service层的代码如下:
//转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); //模拟异常: int j = 1/0; }
-
dao层的代码保持不变,两个事务方法的事务传播行为都是REQUIRES_NEW
-
查看运行结果
IDEA报算术异常:
查看数据库中的数据:
不出意外,两个Dao层的方法的修改都生效了。说明:因为两个Dao层的方法都是事务方法,且传播行为为REQUIRES_NEW,因此当被调用时,会独立新开一个自己的事务进行数据库操作,将被调用者(即 transfer())的事务挂起,当Dao层的两个事务方法各自执行完数据库操作后,会立即提交自己的事务,数据库的修改立即生效。后面transfer() 虽然发生了算术异常,它所在的事务回滚,也不影响之前已经提交了的事务。
实验三:调用者正常执行,让第一个被调用者出现异常:
将数据库中的数据进行还原,然后实验:
- 修改Dao层中的reduceBalance(),开启模拟异常,addBalance() 保持不变:
@Override @Transactional(propagation = Propagation.REQUIRES_NEW) public int reduceBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money - ? where t_id = ? ;"; //模拟异常: int i = 1 / 0; return jdbcTemplate.update(sql, money, id); }
- Service层的代码关闭模拟异常,使其正常执行:
//转账业务:用户1给用户2转账100元 @Transactional public void transfer(){ //用户1 减少 100元 balanaceDao.reduceBalance(1, new BigDecimal(100)); //用户2 增加 100元 balanaceDao.addBalance(2, new BigDecimal(100)); }
-
查看运行结果:
IDEA依然报错:
查看数据库结果:
并未发生任何修改。说明:reduceBalance() 和 addBalance() 虽然都会开启独立的事务,不与transfer() 共享同一个事务。但由于transfer() 是 先调用了 reduceBalance() ,而 reduceBalance() 中出现了异常,且未被进行处理,因此当它出现了异常后,reduceBalance() 自己的事务就会被回滚,接着异常被传递到了调用者,也就是transfer()中,而它也未进行处理,因此它所在的事务也会被回滚,而addBalance() 因为异常而未能被执行到,所以最后的结果就是数据库并未发生任何修改。
实验四:调用者正常执行,让第二个被调用者出现异常:
将数据库中的数据还原,然后开始实现:
- Dao层中的reduceBalance() 关闭模拟异常,而addBalance()开启模拟异常,两者都是事务方法,事务传播行为都为REQUIRES_NEW:
@Override @Transactional(propagation = Propagation.REQUIRES_NEW) public int addBalance(Integer id, BigDecimal money) { String sql = "update t_balance set t_money = t_money + ? where t_id = ? ;"; //模拟异常: int i = 1 / 0; return jdbcTemplate.update(sql, money, id); }
-
Service层的transfer()保持不变
-
运行查看结果:
IDEA仍然报错:
数据库中的数据发生了更改:
可以看到,reduceBalance() 操作生效了。说明:借鉴实验三,我们其实已经可以理解了,reduceBalance() 独立开启事务操作数据库后提交了事务;接着 addBalance() 独立开启事务操作数据库,但操作过程中发生了算术异常,事务回滚,且由于未对异常进行操作,因此异常传递给了调用者即transfer(),而调用者也未对异常进行处理,因此调用者的事务也同样发生了回滚,程序结束。
总结:
当事务的属性 propagation 为 REQUIRES_NEW 时,该事务方法不仅必须运行在事务中,而且还必须是运行在自己新开的事务中(重新获取一条新的数据库连接)。若调用者(调用了该事务方法的方法)本身已开启了事务(即已经获取了数据库的连接),该事务方法也不会与它共用同一个事务(即同一条数据库连接)。若调用者自身不是事务方法,没有开启事务,那么该事务方法会自己获取一条数据库连接以开启事务,并在执行完自己的数据库操作后关闭该连接。
除此之外,对于REQUIRES_NEW的事务,其是否会影响到调用者的事务,要根据异常是否被处理而定。
画个示意图:
核心总结:整个混合嵌套事务里,任何处出现异常,在异常出现前,已经执行的REQUIRES_NEW事务都会成功。
几个注意点:
- 如果是REQUIRED,子事务(被调用者)的属性都是继承于大事务(调用者)的;当子事务与大事务的属性不一致时,以大事务的为准。
而REQUIRES_NEW子事务可以调整自己的事务属性,不与大事务保持一致。 - 底层实现:
REQUIRED,是将调用者所在的大事务所使用的数据库连接传递给被调用的事务方法使用。
REQUIRES_NEW:该事务方法直接重新获取一个新的数据库连接进行使用。 - 本类方法的内部嵌套调用,事务控制不起作用,全程都是一个事务,即最外层的调用者的大事务。
原因:事务管理的底层是AOP,AOP的底层是动态代理,只有经过了动态代理对象调用方法,才能使方法被增强,也就是被加上事务管理。而类的内部进行方法嵌套调用,并没有通过代理对象进行方法的调用,因此不走代理模式,也就是AOP没有起到作用,所以事务管理也无法起作用。
好了,以上就是我个人对本次内容的理解与解析,如果有什么不恰当的地方,还望各位兄弟在评论区指出哦。
如果这篇文章对你有帮助的话,不妨点个关注吧~
期待下次我们共同讨论,一起进步~ -
Spring之事务传播行为
2020-05-10 23:07:06首先简单了解一下Spring中事务传播行为是什么?听起来很高端,但是真正用起来的时候,稍有不慎,就会让自己陷入困境之中,所以在使用之前,我们必须要十分耐心认真的学习它。 从名字理解起来,事务传播行为,既然为...一、概念
首先简单了解一下Spring中事务传播行为是什么?听起来很高端,但是真正用起来的时候,稍有不慎,就会让自己陷入困境之中,所以在使用之前,我们必须要十分耐心认真的学习它。
从名字理解起来,事务传播行为,既然为传播就肯定发生在两个实体之间,否则单个实体又如何发生行为呢。通俗点讲就是“一个巴掌拍不响”。下面进入正规话题。- 事务传播行为主要用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务中,该事务如何传播。这个概述可能不好理解,换句话就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
下面用代码+文字说明解释上面的概念。
@Transaction(Propagation=XXX) public void methodA(){ methodB(); //doSomething } @Transaction(Propagation=XXX) public void methodB(){ //doSomething }
methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
注意:methodA和methodB都加了事务。methodA()也可以不用开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。二、Spring中七种事务传播行为
通过上面伪代码加文字解释了解到事务传播行为的相关概念,下面就要学习事务传播行为的类型和运行机制。
事务传播行为类型 解释说明 Propagation_Required 表示被修饰的方法必须运行在事务中。如果当前方法没有事务,则就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。此类型是最常见的默认选择 Propagation_Supports 表示被修饰的方法不需要事务上下文。如果当前方法存在事务,则支持当前事务执行;如果当前没有事务,就以非事务方式执行。 Propagation_Mandatory 表示被修饰的方法必须在事务中运行。如果当前事务不存在,则会抛出一个异常。 Propagation_Required_New 表示被修饰的方法必须运行在它自己的事务中。一个新的事务会被启动。如果调用者存在当前事务,则在该方法执行期间,当前事务会被挂起。 Propagation_Not_Supported 表示被修饰的方法不应该运行在事务中。如果调用者存在当前事务,则该方法运行期间,当前事务将被挂起。 Propagation_Never 表示被修饰的方法不应该运行事务上下文中。如果调用者或者该方法中存在一个事务正在运行,则会抛出异常。 Propagation_Nested 表示当前方法已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立与当前事务进行单独地提交或者回滚。如果当前事务不存在,那么其行为与Propagation_Required一样。 验证
Propagation_Required
- 调用者方法不存在事务传播行为
- 调用者方法内部存在异常时,被调用者方法均存在事务,那么结果如何呢?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertClass(ClassDo classDo) { classMapper.insertClass(classDo); System.out.println("----------------------->Class插入成功!"); } 单元测试 @SpringBootTest @RunWith(SpringRunner.class) public class PropagationTest { private final static StudentDo studentDo = new StudentDo(); private final static ClassDo classDo = new ClassDo(); static { studentDo.setClassId(1); studentDo.setStudentName("student1"); studentDo.setAddress("测试"); classDo.setClassName("class_1"); classDo.setClassNo("Class01"); } @Autowired private StudentService studentService; @Autowired private ClassService classService; @Test public void insertTest() { studentService.insertStudent(studentDo); classService.insertClass(classDo); } }
结果:两条数据均被插入数据库。由于外部方法并没有开启事务,所以内部方法均在自己的事务提交或者回滚,因此外部方法中存在异常,内部方法事务不会回滚。- 被调用者均存在事务,而在被调用者中存在异常,那么结果如何?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } //此方法中抛出异常 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertClassByException(ClassDo classDo) throws CustomException { classMapper.insertClass(classDo); throw new CustomException(); } 单元测试代码 private final static StudentDo studentDo = new StudentDo(); private final static ClassDo classDo = new ClassDo(); static { studentDo.setClassId(2); studentDo.setStudentName("student2"); studentDo.setAddress("测试2"); classDo.setClassName("class_2"); classDo.setClassNo("Class02"); } @Test public void insertExceptionTest() throws CustomException { studentService.insertStudent(studentDo); classService.insertClassByException(classDo); }
结果 第一数据成功插入,第二条数据因异常存在,事务回滚。内部方法均在各个的事务中运行,class事务回滚,student数据不会受到影响。
结合1和2我们可以得出结论1:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation_Required修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。- 2.调用者开启事务传播行为
内部方法同上 //单元测试方法 @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionThrowsTest() throws CustomException { studentService.insertStudent(studentDo); classService.insertClassByException(classDo); }
结果:内部方法虽然存在事务传播行为,但是外部方法也存在事务且使用Propagation.REQUIRED修饰,所有内部方法不会新建事务,直接运行在当前事务中,所以student、class均会被回滚。
- 3.调用者开启事务传播行为,但是捕获内部方法异常
/** * 内部方法发生异常情况,外部方法即使捕获处理该异常,依然数据会被回滚 */ @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionTest() { studentService.insertStudent(studentDo); try { classService.insertClassByException(classDo); } catch (CustomException e) { e.printStackTrace(); } }
结果:外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。同2一样,调用者方法执行操作和被调用者中的方法操作结果均被回滚。
Propagation_Supports
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertSupportsTest() { studentService.insertStudent(studentDo); }
解释:如果单纯的调用insertStudent()方法,则以非事务执行,即使后面存在异常情况,执行操作结果不会触发事务回滚机制。当调用insertSupportsTest()方法时,该方法以REQUIRED修饰,则会新建一个事务,内部调用insertStudent()方法,所以insertStudent()会加入到当前事务中执行。
Propagation_Mandatory
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertSupportsTest() { studentService.insertStudent(studentDo); }
解释结果:MANDATORY表示被修饰的方法必须在事务中运行。当单独调用insertStudent时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用insertSupportsTest时,insertStudent则加入到insertSupportsTest的事务中,事务地执行。Propagation_Required_New
表示被修饰的方法必须运行在它自己的事务中。一个新的事务会被启动。如果调用者存在当前事务,则在该方法执行期间,当前事务会被挂起。
private final static StudentDo studentDo = new StudentDo(); private final static ClassDo classDo = new ClassDo(); static { studentDo.setClassId(2); studentDo.setStudentName("requireNew"); studentDo.setAddress("requireNew"); } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void insertClassByException(ClassDo classDo) throws CustomException { classMapper.insertClass(classDo); throw new CustomException(); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionTest() { studentService.insertStudent(studentDo); try { classService.insertClassByException(classDo); } catch (CustomException e) { e.printStackTrace(); } }
结果解析:insertStudent(),insertClassByException()方法执行时,外部方法事务被挂起,内部方法会新建事务,直至该方法执行结束,恢复外部方法事务执行。两者之间事务存在隔离性,insertClassByException()方法遇到异常,触发事务回滚机制,但insertStudent()执行结果并受到影响。
如图所示:
Propagation_Not_Supported
表示被修饰的方法不应该运行在事务中。如果调用者存在当前事务,则该方法运行期间,当前事务将被挂起。
private final static ClassDo classDo = new ClassDo(); static { classDo.setClassName("notSupport"); classDo.setClassNo("notSupport"); } @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public void insertClassByException(ClassDo classDo) throws CustomException { classMapper.insertClass(classDo); throw new CustomException(); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionTest() { try { classService.insertClassByException(classDo); } catch (CustomException e) { e.printStackTrace(); } }
结果解释:即使外部方法开启事务,但是insertClassByException()执行,当前事务会挂起,not_support以非事务方式运行,所以即使遇到异常情况,执行结果也不会触发回滚。
Propagation_Never
表示被修饰的方法不应该运行事务上下文中。如果调用者或者该方法中存在一个事务正在运行,则会抛出异常。
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionTest() { studentService.insertStudent(studentDo); }
结果如图:
Propagation_Nested
表示当前方法已经存在一个事务,那么该方法将会在嵌套事务中运行。
嵌套的事务可以独立与当前事务进行单独地提交或者回滚。
如果当前事务不存在,那么其行为与Propagation_Required一样。
嵌套事务的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。- 1.外部未开启事务时,内部方法则新建事务执行
private final static StudentDo studentDo = new StudentDo(); private final static ClassDo classDo = new ClassDo(); static { studentDo.setClassId(2); studentDo.setStudentName("NESTED"); studentDo.setAddress("NESTED"); classDo.setClassName("NESTED"); classDo.setClassNo("NESTED"); } @Test public void insertTest() { studentService.insertStudent(studentDo); classService.insertClass(classDo); throw new RuntimeException(); } @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void insertStudent(StudentDo studentDo) { studentMapper.insertStudent(studentDo); System.out.println("----------------------->Student插入成功!"); } @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void insertClass(ClassDo classDo) { classMapper.insertClass(classDo); System.out.println("----------------------->Class插入成功!"); }
结果:
- 2.外部方法开启事务:
- 如果外部方法发生异常,则内部事务一起发生回滚操作;
- 如果外部无异常情况,内部被调用方法存在异常情况,则内部方法独立回滚(疑问点???我用以下实例验证,但是外部方法也一样被回滚了,请各位大佬给与解答);
//单测代码 private final static StudentDo studentDo = new StudentDo(); private final static ClassDo classDo = new ClassDo(); static { studentDo.setClassId(2); studentDo.setStudentName("NESTED_InnerException"); studentDo.setAddress("NESTED_InnerException"); classDo.setClassName("NESTED_InnerException"); classDo.setClassNo("NESTED_InnerException"); } @Test @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void insertInnerExceptionThrowsTest() throws CustomException { studentMapper.insertStudent(studentDo); classService.insertClassByException(classDo); } //NESTED事务传播行为 @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void insertClassByException(ClassDo classDo) throws CustomException { classMapper.insertClass(classDo); throw new RuntimeException(); }
源代码传送门:gitHub仓库
-
Spring事务的传播行为
2019-02-23 23:16:29本文主要介绍下Spring事务中的传播行为。 事务传播行为介绍 Spring中的7个事务传播行为: 事务行为 说明 PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务 ... -
spring事务传播行为
2022-02-15 14:34:31什么是spring事务传播行为? 事务传播行为: 指的是当前一个事务方法B被另一个事务方法A调用时, 这个事务方法应该如何进行. 例如: 方法A事务方法调用方法B事务方法时, 方法B是和方法A采用同一个事务, 还是单独开启一个... -
事务的7种传播行为
2020-09-21 14:51:55一、什么是事务传播行为? 事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。 例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA... -
Spring事务的传播行为和隔离级别
2020-12-24 05:34:151.事务的传播行为 事务的使用过程中,用的最多的传播行为是require,在大部分的mis系统里,可以对整个业务层切一个require的事务就可以满足需要。 但spring提供的不仅如此,对于复杂的业务,Spring也提供了相应的... -
Spring事务传播行为问题解决
2020-08-25 04:49:52主要介绍了Spring事务传播行为问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
看完就明白_spring事务的7种传播行为
2018-06-15 17:07:45原文:https://blog.csdn.net/soonfly/article/details/70305683事务传播行为...事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA事... -
Spring 事务隔离级别和传播行为
2021-11-15 23:50:35二、spring支持7种事务传播行为 传播行为 含义 propagation_required(xml文件中为required) 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则... -
深入理解Spring的事务传播行为
2020-08-27 23:17:58spring特有的事务传播行为,spring支持7种事务传播行为,确定客户端和被调用端的事务边界(说得通俗一点就是多个具有事务控制的service的相互调用时所形成的复杂的事务边界控制),这篇文章主要给大家介绍了关于... -
Spring中事务的传播行为有哪些?
2022-04-16 21:03:20什么是事务传播行为 所为的事务传播行为就是多个声明的事务的方法 相互调用的时候,这个事务该如何传递 如图,methodA()调用methodB()那么这两个方法都显示了开启事务,那么methodB()开启一个新的事务,还是继续在... -
人工智能环境下传播主体的政治传播行为分析.pdf
2021-07-11 08:10:12人工智能环境下传播主体的政治传播行为分析.pdf -
消费者口碑传播行为及交互影响的模拟仿真研究 (2014年)
2021-06-17 13:59:47基于元胞自动机模型对消费者正面口碑、负面口碑和中立口碑传播行为之间的影响作用和动态演变进行了模拟仿真,讨论了在不同消费者初始状态、行为保持性、行为传播性和实施不同政策力度条件下,消费者口碑传播行为演化... -
Spring的七种事务传播行为
2021-10-28 22:55:06一、什么是事务传播行为? 事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。 例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA的... -
深入理解Spring事务的传播行为
2020-08-26 07:37:11Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。下面这篇文章主要给大家介绍了关于Spring事务传播行为的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... -
MySql事务传播行为
2021-01-18 22:22:29传播特性该特性是保证事务是否开启,业务逻辑是否使用同一个事务的保证。当事务在传播过程中会受其影响。其传播特性包括:1、Propagation.REQUIRED方法被调用时自动开启事务,在事务范围内使用则使用同一个事务,... -
传播学原理传播行为二PPT课件.pptx
2021-10-07 08:46:20传播学原理传播行为二PPT课件.pptx -
Spring七种事务传播行为
2020-01-16 20:24:17事务传播行为 “事务传播行为”描述的是:当一个事务方法被另一个方法调用时,该事务方法如何进行? 是创建新事务?丢弃事务?还是加入到已存在的事务呢? 针对这些情况,Spring框架定义了七种事务传播行为,开发... -
图解spring中七种事务传播行为 终于有人讲明白了
2021-05-08 14:05:22什么是传播行为? 默认情况下,只有一个事务,所有的修改操作都在一个事务里面,要么一起提交,要么一起回滚,这没什么问题。但要是有2个或者2个事务以上该如何解决呢? 既然是传播,那么至少要有2个东西,才... -
论文研究-消费者口碑传播行为及交互影响的模拟仿真研究.pdf
2019-07-22 23:17:12基于元胞自动机模型对消费者正面口碑、负面口碑和中立口碑传播行为之间的影响作用和动态演变进行了模拟仿真,讨论了在不同消费者初始状态、行为保持性、行为传播性和实施不同政策力度条件下,消费者口碑传播行为演化... -
浅谈Spring事务传播行为实战
2020-08-25 16:46:22主要介绍了浅谈Spring事务传播行为实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
传播学原理传播行为二PPT学习教案.pptx
2021-10-07 09:06:26传播学原理传播行为二PPT学习教案.pptx -
@Transactional的七种事务传播行为
2021-11-18 17:22:06@Transactional REQUIRED (默认事务) 支持当前事务;如果当前没有事务,则新建一个事物 REQUIRES_NEW (a事务失败了不会影响b事务的执行) 新建...NESTED 如果当前存在事务,则对该传播行为修饰的方法回依然使用当前事务 -
Spring:7种事务传播行为
2018-04-23 08:55:447种事务传播行为所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring支持以下7种事务传播行为。 传播行为含义PROPAGATION_REQUIRED(XML文件中为REQUIRED)表示当前方法必须在一个具有...