1. 程式人生 > >一次Spring Transactional嵌套事務使用不同的rollbackFor的分析

一次Spring Transactional嵌套事務使用不同的rollbackFor的分析

eight HR src rollback ini 執行 如果 問題 進行

  起因:

    項目期間由於一次異常回滾問題,發現自己在事務知識方面知識的遺漏,趁著這次機會,做了幾次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的分析