一次Spring Transactional嵌套事務使用不同的rollbackFor的分析
起因:
項目期間由於一次異常回滾問題,發現自己在事務知識方面知識的遺漏,趁著這次機會,做了幾次rollbackFor的測試。
測試:
現在有兩個事務,事務oute包含事務Inner。事務A回滾規則是當事務拋出TestException,其中TestException繼承RunTimeException。事務B的回滾規則是事務拋RuntimeException。事務的傳播方式都是使用的默認,即 Propagation.REQUIRED。如以下代碼:
1 @Override 2 @Transactional(rollbackFor = TestException.class) 3 public void transOuter() { 4 productMapper.updateOrderQuantityPessimistic(product_code1); 5 ((ProductService) AopContext.currentProxy()).transInner(); 6 } 7 8 @Transactional(rollbackFor = Exception.class) 9 public void transInner() { 10 productMapper.updateOrderQuantityPessimistic(product_code);11 if (true) { 12 throw new RuntimeException(); 13 } 14 }
以下為TestException的代碼。
1 public class TestException extends RuntimeException { 2 3 public TestException(String message) { 4 super(message); 5 } 6 }
一開始按照自己對事務的理解, 默認的傳播屬性之下。事務B啟動的時候,會默認使用事務A的rollbackFor來進行回滾,所以該代碼運行時候。程序不會回滾。
然而測試,測試完之後發現事務A、B都進行了回滾。
看著測試結果產生了疑問。難道是以innner的rollBack為準?接著進行測試。
1 @Override 2 @Transactional(rollbackFor = Exception.class) 3 public void transOuter() { 4 productMapper.updateOrderQuantityPessimistic(product_code1); 5 ((ProductService) AopContext.currentProxy()).transInner(); 6 } 7 8 @Transactional(rollbackFor = TestException.class) 9 public void transInner() { 10 productMapper.updateOrderQuantityPessimistic(product_code); 11 if (true) { 12 throw new RuntimeException(); 13 } 14 }
再次測試,測試完之後發現事務A、B依然進行了回滾。
感覺自己對事務的理解還是太淺薄了,是時候debug一波源碼。
分析源碼:
查看 org.springframework.transaction.interceptor.TransactionAspectSupport 類的 invokeWithinTransaction方法。該方法是事務執行的主要方法,這裏我們主要看第20行的事務捕捉那一塊。completeTransactionAfterThrowing的方法。
1 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) 2 throws Throwable { 3 4 // If the transaction attribute is null, the method is non-transactional. 5 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); 6 final PlatformTransactionManager tm = determineTransactionManager(txAttr); 7 final String joinpointIdentification = methodIdentification(method, targetClass); 8 9 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { 10 // Standard transaction demarcation with getTransaction and commit/rollback calls. 11 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); 12 Object retVal = null; 13 try { 14 // This is an around advice: Invoke the next interceptor in the chain. 15 // This will normally result in a target object being invoked. 16 retVal = invocation.proceedWithInvocation(); 17 } 18 catch (Throwable ex) { 19 // 事務異常捕捉主要在這邊獲取 20 completeTransactionAfterThrowing(txInfo, ex); 21 throw ex; 22 } 23 finally { 24 cleanupTransactionInfo(txInfo); 25 } 26 commitTransactionAfterReturning(txInfo); 27 return retVal; 28 } 29 30 else { 31 // It‘s a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. 32 try { 33 Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, 34 new TransactionCallback<Object>() { 35 @Override 36 public Object doInTransaction(TransactionStatus status) { 37 TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); 38 try { 39 return invocation.proceedWithInvocation(); 40 } 41 catch (Throwable ex) { 42 if (txAttr.rollbackOn(ex)) { 43 // A RuntimeException: will lead to a rollback. 44 if (ex instanceof RuntimeException) { 45 throw (RuntimeException) ex; 46 } 47 else { 48 throw new ThrowableHolderException(ex); 49 } 50 } 51 else { 52 // A normal return value: will lead to a commit. 53 return new ThrowableHolder(ex); 54 } 55 } 56 finally { 57 cleanupTransactionInfo(txInfo); 58 } 59 } 60 }); 61 62 // Check result: It might indicate a Throwable to rethrow. 63 if (result instanceof ThrowableHolder) { 64 throw ((ThrowableHolder) result).getThrowable(); 65 } 66 else { 67 return result; 68 } 69 } 70 catch (ThrowableHolderException ex) { 71 throw ex.getCause(); 72 } 73 } 74 }
這邊顯示當事務的rollbackFor為TestException,而拋出的異常為RunTimeException時候。跟我們的transInner一致。接著往下看 completeTransactionAfterThrowing 方法。主要看第8行,第8行對事務進行判斷,是否對該拋出的異常進行回滾。
1 protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) { 2 if (txInfo != null && txInfo.hasTransaction()) { 3 if (logger.isTraceEnabled()) { 4 logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + 5 "] after exception: " + ex); 6 } 7 //這裏主要判斷事務捕獲了異常以後,是否進行回滾 8 if (txInfo.transactionAttribute.rollbackOn(ex)) { 9 try { 10 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 11 } 12 catch (TransactionSystemException ex2) { 13 logger.error("Application exception overridden by rollback exception", ex); 14 ex2.initApplicationException(ex); 15 throw ex2; 16 } 17 catch (RuntimeException ex2) { 18 logger.error("Application exception overridden by rollback exception", ex); 19 throw ex2; 20 } 21 catch (Error err) { 22 logger.error("Application exception overridden by rollback error", ex); 23 throw err; 24 } 25 } 26 else { 27 // We don‘t roll back on this exception. 28 // Will still roll back if TransactionStatus.isRollbackOnly() is true. 29 try { 30 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); 31 } 32 catch (TransactionSystemException ex2) { 33 logger.error("Application exception overridden by commit exception", ex); 34 ex2.initApplicationException(ex); 35 throw ex2; 36 } 37 catch (RuntimeException ex2) { 38 logger.error("Application exception overridden by commit exception", ex); 39 throw ex2; 40 } 41 catch (Error err) { 42 logger.error("Application exception overridden by commit error", ex); 43 throw err; 44 } 45 } 46 } 47 }
再深入點進去看,看到第11行,這邊獲取該異常的深度。跳轉到片段二進行代碼查看。
1 public boolean rollbackOn(Throwable ex) { 2 if (logger.isTraceEnabled()) { 3 logger.trace("Applying rules to determine whether transaction should rollback on " + ex); 4 } 5 6 RollbackRuleAttribute winner = null; 7 int deepest = Integer.MAX_VALUE; 8 9 if (this.rollbackRules != null) { 10 for (RollbackRuleAttribute rule : this.rollbackRules) { 11 //獲取異常的深度? 12 int depth = rule.getDepth(ex); 13 if (depth >= 0 && depth < deepest) { 14 deepest = depth; 15 winner = rule; 16 } 17 } 18 } 19 20 if (logger.isTraceEnabled()) { 21 logger.trace("Winning rollback rule is: " + winner); 22 } 23 24 // User superclass behavior (rollback on unchecked) if no rule matches. 25 if (winner == null) { 26 logger.trace("No relevant rollback rule found: applying default rules"); 27 //如果depth為-1之後,父類的回滾方式 28 return super.rollbackOn(ex); 29 } 30 31 return !(winner instanceof NoRollbackRuleAttribute); 32 }
根據深度代碼查看,rollbackFor和拋出異常ex不一致,返回-1。再回去看上面的代碼片段,當返回-1之後,代碼走到第28行。進行父類的回滾方法。
1 private int getDepth(Class<?> exceptionClass, int depth) { 2 if (exceptionClass.getName().contains(this.exceptionName)) { 3 // Found it! 4 return depth; 5 } 6 // If we‘ve gone as far as we can go and haven‘t found it... 7 //此處RuntimeException 跟TestException不一致,返回-1 8 if (exceptionClass == Throwable.class) { 9 return -1; 10 } 11 return getDepth(exceptionClass.getSuperclass(), depth + 1); 12 }
以下是父類的代碼是否回滾判斷方法,有沒有很眼熟,只要拋出的異常的是RunTimeExcpetion或者Error則進行回滾。
1 public boolean rollbackOn(Throwable ex) { 2 return (ex instanceof RuntimeException || ex instanceof Error); 3 }
總結
根據上面的代碼,我們可以推斷出以下幾個結論:
1、當我們拋出的異常為RunTime及其子類或者Error和其子類的時候。不論rollbackFor的異常是啥,都會進行事務的回滾。
2、當我們拋出的異常不是RunTime及其子類或者Error和其子類的時候,必須根據rollbackfor進行回滾。比如rollbackfor=RuntimeException,而拋出IOException時候,事務是不進行回滾的。
3、當我們拋出的異常不是RunTime及其子類或者Error和其子類的時候,如果嵌套事務中,只要有一個rollbackfor允許回滾,則整個事務回滾。
經過測試,上述的結論也沒發現什麽問題。
一次Spring Transactional嵌套事務使用不同的rollbackFor的分析