javascript
Spring事务的处理流程、传播属性、及部分释疑
目錄
文章目錄
- 一、spring事務(wù)利用AOP的攔截和處理流程
- 必須記住的兩個要點(diǎn)
- 二、事務(wù)傳播屬性
- 三、為什么很多Exception異常必須配置在rollback-for中才有用
- 四、事務(wù)的傳播性在同一個類中方法互調(diào)時為什么會失效?
spring或是AOP參考: spring概要
一、spring事務(wù)利用AOP的攔截和處理流程
如果知道了事務(wù)的處理流程,去理解事務(wù)傳播屬性導(dǎo)致的回滾就是分分鐘的事兒了。
想要知道事務(wù)的攔截處理流程,只需要分析TransactionAspectSupport類的部分源碼即可
// 事務(wù)攔截處理方法 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);final String joinpointIdentification = this.methodIdentification(method, targetClass);// 以下代碼只是部分代碼,為了方便看只截取了能說明問題的部分// 1.獲取事務(wù)TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 2.處理真正調(diào)用的方法retVal = invocation.proceedWithInvocation();} catch (Throwable var15) {// 3.有異常回滾:this.completeTransactionAfterThrowing(txInfo, var15);throw var15;} finally {this.cleanupTransactionInfo(txInfo);}// 4.處理方法完成,提交事務(wù)this.commitTransactionAfterReturning(txInfo);return retVal;}從上面注釋可以看到,任何一個被事務(wù)攔截的方法,都是先在真正調(diào)用該方法之前獲取了事務(wù),執(zhí)行完該方法后再決定是事務(wù)回滾或提交。
我們還可以看到,調(diào)用處理真正要執(zhí)行的方法是被try catch的,而catch塊里才會有事務(wù)回滾的代碼。所以,如果某個事務(wù)方法A內(nèi)部有異常沒有拋出來(被自己的try cathch塊捕獲了),而不能被這里的try catch塊捕獲到,那么這個方法A就不會被回滾。
不會回滾的例子:
將會回滾的例子:
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)@Overridepublic void saveTx() { Log log = new Log();log.setTitle("saveTx");log.setBeginDate(new Date());logService.save(log);int i=0;int j=2/i; }在我們寫代碼的時候,有時候會考慮某個方法是否應(yīng)該被回滾,那么我們就得注意方法里的異常是否該被try catch是非常重要的。
還有一點(diǎn),如果一個事務(wù)方法調(diào)用了一個非事務(wù)方法,那么非事務(wù)方法就要看作是事務(wù)方法的一部分,它們共用的一個數(shù)據(jù)庫連接。
必須記住的兩個要點(diǎn)
二、事務(wù)傳播屬性
表格中只要說到加入父事務(wù)后,就為同一個事務(wù),他們會共用一個Connection
| 傳播屬性 | 說明 |
| required | 如果當(dāng)前存在事務(wù)則加入該事務(wù),如果沒有則新建一個事務(wù) |
| supports | 如果當(dāng)前存在事務(wù)則加入該事務(wù),如果沒有則以非事務(wù)的方式運(yùn)行。 |
| mandatory | 強(qiáng)制加入當(dāng)前的事務(wù)。如果當(dāng)前沒有事務(wù),就拋出異常。 |
| requires_new | 新建事務(wù)。如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。 |
| not_supported | 不支持事務(wù)。如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起,然后以非事務(wù)方式執(zhí)行。 |
| never | 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。 |
| nested | 如果當(dāng)前存在事務(wù),則嵌套執(zhí)行(相當(dāng)于指定了回滾點(diǎn)SavePoint)。如果當(dāng)前沒有事務(wù),則新建事務(wù)。 |
三、為什么很多Exception異常必須配置在rollback-for中才有用
經(jīng)歷過的都知道,Exception異常如果不在rollback-for屬性當(dāng)中指定,即使出現(xiàn)了Exception異常也不會發(fā)生事務(wù)回滾。
這是因為spring事務(wù)處理時,只對RuntimeException和Error異常進(jìn)行了處理,而Exception沒有在其中。
我們看看TransactionAspectSupport類回滾方法里的代碼:下面代碼只看注釋就行了
protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.hasTransaction()) {if (this.logger.isTraceEnabled()) {this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);}// 判斷是否是要回滾的異常:rollbackOn方法代碼在后面if (txInfo.transactionAttribute.rollbackOn(ex)) {try {// 回滾txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());} catch (TransactionSystemException var7) {this.logger.error("Application exception overridden by rollback exception", ex);var7.initApplicationException(ex);throw var7;} catch (RuntimeException var8) {this.logger.error("Application exception overridden by rollback exception", ex);throw var8;} catch (Error var9) {this.logger.error("Application exception overridden by rollback error", ex);throw var9;}// 不在指定異常范圍內(nèi)} else {try {// 事務(wù)提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());} catch (TransactionSystemException var4) {this.logger.error("Application exception overridden by commit exception", ex);var4.initApplicationException(ex);throw var4;} catch (RuntimeException var5) {this.logger.error("Application exception overridden by commit exception", ex);throw var5;} catch (Error var6) {this.logger.error("Application exception overridden by commit error", ex);throw var6;}}}}異常判斷方法:
// RuleBasedTransactionAttribute類的方法 public boolean rollbackOn(Throwable ex) {if (logger.isTraceEnabled()) {logger.trace("Applying rules to determine whether transaction should rollback on " + ex);}RollbackRuleAttribute winner = null;int deepest = 2147483647;if (this.rollbackRules != null) {Iterator var4 = this.rollbackRules.iterator();// 遍歷rollback規(guī)則while(var4.hasNext()) {RollbackRuleAttribute rule = (RollbackRuleAttribute)var4.next();// 查找當(dāng)前異常是否在規(guī)則里面int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}if (logger.isTraceEnabled()) {logger.trace("Winning rollback rule is: " + winner);}// 該異常沒在rollback規(guī)則里,則再去判斷是否是RuntimeException或Errorif (winner == null) {logger.trace("No relevant rollback rule found: applying default rules");// 這里調(diào)用的方法參考return super.rollbackOn(ex);} else {// rollback規(guī)則里有此異常就返true,即要回滾return !(winner instanceof NoRollbackRuleAttribute);}}代碼片段3:
// 如果是RuntimeException或Error異常的子類就返回truepublic boolean rollbackOn(Throwable ex) {// 只指定了這兩個異常,沒有Exception異常return ex instanceof RuntimeException || ex instanceof Error;}rollback-for屬性只針對Error和RuntimeException
我在網(wǎng)上找了一個特別合適的類圖來說明:
四、事務(wù)的傳播性在同一個類中方法互調(diào)時為什么會失效?
我們知道,spring的事務(wù)都是通過AOP動態(tài)代理實現(xiàn)的。如果想要任何一個方法實現(xiàn)事務(wù)代理,就必須通過事務(wù)代理類去調(diào)用,而內(nèi)部方法調(diào)用與事務(wù)代理類一點(diǎn)我關(guān)系都木有,所以不會生效。下面我舉個例子
要代理的接口類:
public interface Subject {void methodOne();void methodTwo(); }要代理的真實類:
public class RealSubject implements Subject {@Overridepublic void methodOne() {System.out.println("one");this.menthoTwo();}@Overridepublic void methodTwo() {System.out.println("two");} }代理類要調(diào)用的事務(wù)處理器(代理類就是通過它對方法實現(xiàn)的額外處理):
public class MyInvocationHandler implements InvocationHandler {private Object obj;public MyInvocationHandler(){}public MyInvocationHandler(Object obj){this.obj = obj;}// 調(diào)用方法前的額外處理:記錄方法調(diào)用開始時間public void startRecordRequestTime(){System.out.println("方法調(diào)用開始時間:"+System.currentTimeMillis());}// 調(diào)用方法前的額外處理:模擬處理事務(wù)的傳播屬性public void processTransaction(){System.out.println("事務(wù)處理..........");}// 調(diào)用方法后的額外處理:記錄方法調(diào)用結(jié)束時間public void endRecordRequestTime(){System.out.println("方法調(diào)用結(jié)束時間:"+System.currentTimeMillis());}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {this.startRecordRequestTime();// 調(diào)用處理事務(wù)傳播屬性方法this.processTransaction();// 通過反射執(zhí)行真實的方法method.invoke(obj,args);this.endRecordRequestTime();return null;} }使用代理訪問:
public class Test { public static void main(String[] args) {Subject sub = new RealSubject();MyInvocationHandler handler = new MyInvocationHandler(sub);// 利用反射動態(tài)生成的代理類Subject proxy=(Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},handler);// 通過代理類訪問方法proxy.methodOne();} }//運(yùn)行結(jié)果為: 方法調(diào)用開始時間:1531564567403 事務(wù)處理.......... one two 方法調(diào)用結(jié)束時間:1531564567403 .好了,從執(zhí)行了proxy.methodOne();這句代碼后的結(jié)果能看到,當(dāng)代理類調(diào)用了methodOne()后,methodTwo()是在內(nèi)部調(diào)用的,與代理類一點(diǎn)兒關(guān)系也沒有,所以methodOne調(diào)用內(nèi)部的methodTwo時,是不會走事務(wù)處理代碼的,所以事務(wù)傳播屬性也就會失效。
上面動態(tài)生成的代理類大概如下,有興趣的可以了解下,重點(diǎn)是下面的重寫方法調(diào)用的MyInvocationHandler 類的invoke方法:
總結(jié)
以上是生活随笔為你收集整理的Spring事务的处理流程、传播属性、及部分释疑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java单例模式双重检查锁定中volat
- 下一篇: 深究angularJS——(上传)Fil