前两篇写了将Spring AOP应用与入参校验和拦截方法进行日志打印。本篇继续应用三AOP应用于事务管理,顺便把事务相关技术点统一Review一遍。(同样基于case)
一、事务管理回顾
基于用户支付成功后,创建支付订单和支付记录(两个表的insert操作)来回顾事务管理。一共涉及3个方法
1)创建pay支付记录逻辑 2)创建order支付订单逻辑 3)组合方法:调用方法1、2,同步创建支付记录和支付订单
下表case默认(表1)
1、保证事务的均采用Required传播属性。
2、执行方法2先,1后,因为都在1中写的业务异常测试
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
无 throw |
有 |
有 |
12均成功,后抛出异常 |
|
无 throw |
有(业务异常) |
有 |
2成功,1失败 |
|
有 throw |
有(业务异常) |
有 |
12均失败 |
|
有 throw |
有 |
有 |
12均失败 |
1)分析:pay方法执行便立即提交commit,order同理。即便最后抛出异常,结果照样正常入库。
2)分析:同上,pay方法执行insert后立即执行commit请求,支付记录入库。而创建订单逻辑报错,创建失败。
3)分析:尽管pay、order分别存在两个事务,但当组合方法增加事务管理时,在执行pay事务后,并未执行commit请求,而是等着order执行成功后,一并commit。所以在order逻辑报错后,事务回滚,表现就是pay和order均未创建。
4)分析:虽然12逻辑正常,组合方法最后设置抛出异常操作,故组合方法commit不执行,直接rollback
结论
1、当组合方法不添加事务管理时,无论单个方法是否包含事务,也无论报错是原于单个方法中逻辑问题错误,还是两个事务逻辑正常手动抛exeption的异常,背后都是先执行逻辑没问题即刻提交commit,先执行先成功。组合方法无原子性。
2、当组合方法添加事务管理时,无论何种异常,都校验到事务中所有操作均成功后,再提价组合事务commit操作,期间有一个失败,则两单个事务都入库失败。事务性无关单个方法事务,由组合方法事务保证原子性。
注意:
在case3时犯了一个很低级的错误,创建order内部异常时,由于代码主动catch了异常,虽然是三个方法都设置了required事务,但由于catch了第一个方法异常,第二个方法仍会顺序执行,最后一起commit组合方法的提交。所以表象看起来好像外层保证了原子性,但结果确实一张表失败,一张表insert成功。
所以虽然是配置事务传播属性,但还跟代码具体写法相关。尤其是错误处理是否throw出来异常,还是catch住接下来的逻辑正常执行。这个得配合具体业务场景设计。
二、Spring事务传播属性
以下所有表格case默认:有事务表示case中均采用要验证的传播属性,例如本case验证Required,有则表示都设置成的传播Required。非默认传播属性会特别标明。(表2)1、Required
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
无throw |
有 |
有 |
12均成功 |
|
无throw |
有 |
有(异常) |
1成功,2失败 |
|
有throw |
无 |
无(异常) |
12均失败 |
|
有throw |
有 |
有(异常) |
12均失败 |
1)1、2required加入到无事务中,自启事务保证commit操作
2)由于组合方法无事务,当2事务异常,1仍执行成功,表明required所起的新事务,仅保证自身方法原子性。而不保证整个组合方法原子性(否则因为2异常失败,1也该失败)。注意外部无事务保证。
3)组合方法有事务保证,被调起的12无事务,因2的异常,导致组合方法整个事务回滚,12均失败。
4)组合有事务保证,1的required会新起事务,保证1执行成功。2由于异常,失败。但1并未回滚,验证了2)中的说法。
其实“表1”跟“表2”的case基本重合,都验证了两点。
1、当外部操作有事务时,被调起的方法若传播属性是Required,则直接加入到外部事务,即便先执行被调起方法也不会commit,而是由外部事务统一控制commit操作。即在执行被调用方法后,若外部事务执行异常,被调起的方法随外部事务一起回滚。简言之:原操作有事务,加入到原事务中,统一由原事务管理,失败一并回滚。
2、当外部方法无事务保证时,被调起required事务传播属性的事务方法,若原操作无事务,required事务方法将自启事务,但注意!!新起的事务仅保证自身即被调起方法的事务执行,成功即提交,原操作执行失败,被调起事务也不会回滚。即表2中第二行case,1成功2失败。
2、Surport
被调用为sruppot传播属性时,如果原操作有保证事务,即以事务的形式运行,如果原操作无保证事务,那么就以非事务的形式运行
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
无throw |
有 |
无(异常) |
1成功,2失败 |
|
有throw |
有 |
无(异常) |
12均失败 |
|
有Surport throw |
有 |
无(异常) |
1成功,2失败 |
1) 组合方法无事务保证,1以sup,则直接按无事务执行,1成功,2有异常失败
2) 组合方法有事务保证(设置为Required),1以sup,此时加入到组合方法事务中执行,当2异常时,1也回滚,两个操作均失败
3) 组合方法有事务保证(设置为sup),因为程序入口为Test不保证事务,则组合方法便按无事务顺序执行,1也是sup,同理。故1成功,2异常失败。
3、MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
. |
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
无 |
有 |
无 |
12均成功 |
4、REQUIRES_NEW
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
有Re |
有Re_New |
无 |
12均成功 |
|
有Re |
有Re_New |
无(异常) |
1成功,2失败 |
|
有Re |
有Re_New(异常) |
无 |
1失败,2成功 |
1)组合方法事务级别为REQUIRED,到1时,1会新起事务,组合方法事务挂起,等待1事务执行结果后,执行2,12均无异常,均成功。
2)此时若2异常,1在新事务中执行成功,由于2异常组合事务执行失败回滚。而由于1在全新的事务中已commit,故1不会回滚,成功执行。
3)此时如果1异常,回滚。组合中catch住1的异常后,挂起的事务继续执行2,2无异常,组合事务正常commit。
5、NESTED
NESTED与REQUIRES_NEW的区别是,REQUIRES_NEW另起一个事务,将会与他的父事务(组合方法中的事务)相互独立,而Nested的事务和它的父事务是相依的,需要等父事务一块提交的。如果父事务最后回滚,Nested也将回滚不执行commit操作。
6、NOT_SUPPORTED
被调用的方法采用该传播属性时表示我不支持事务。如遇源操作配置需保证事务处理,则原操作执行到调用方法时,事务会被挂起,等待被调用方法顺序执行完毕后,再继续执行原操作的事务。
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
有Re |
有(异常) |
无 |
1异常被catch后,执行2成功 |
|
有Re |
有(异常) |
无(异常) |
1、2均打印异常,2由异常失败 |
1)组合方法已有事务保证,执行到1时,事务挂起,1中异常(使用trycatch打印日志),catch完后,2正常执行
2)当2中也发生异常时,2也失败回滚。
7、NEVER
被调用的方法不能在事务中运行。如遇源操作配置需保证事务处理,则原操作执行到调用方法时,被调用方法直接抛异常。
|
组合 |
Method1 |
Method2 |
执行结果 |
是否有事务 |
有Re throw |
有 |
无 |
12均成功 |
1) 在required事务里里,1也没有抛异常,不起作用(未完待续)