1. 程式人生 > >解決Spring AOP 事務 配置 失效原因--業務類裡丟擲的異常不滿足事務攔截器裡定義的異常

解決Spring AOP 事務 配置 失效原因--業務類裡丟擲的異常不滿足事務攔截器裡定義的異常

採用AOP配置宣告式事務有5種方式,下面只說關於採用TransactionInterceptor事務攔截器的方式,配置程式如下:

transactionManager:

Xml程式碼 複製程式碼
  1. <beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"/>
  2. <beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  3. <propertyname="dataSource"ref="dataSource"/>
  4. </bean>

 TransactionInterceptor:

Xml程式碼 複製程式碼
  1. <beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
  2. <propertyname="transactionManager"ref="transactionManager"/>
  3. <propertyname="transactionAttributes"
    >
  4. <props>
  5. <propkey="add*">PROPAGATION_REQUIRED</prop>
  6. <propkey="del*">PROPAGATION_REQUIRED</prop>
  7. <propkey="update*">PROPAGATION_REQUIRED</prop>
  8. <propkey="query*">readOnly</prop>
  9. <propkey="get*">readOnly</prop>
  10. <propkey="find*">readOnly
    </prop>
  11. <propkey="check*">PROPAGATION_REQUIRED</prop>
  12. <propkey="operate*">PROPAGATION_REQUIRED</prop>
  13. <propkey="batch*">PROPAGATION_REQUIRED</prop>
  14. <propkey="deploy*">PROPAGATION_REQUIRED</prop>
  15. <propkey="exec*">PROPAGATION_REQUIRED</prop>
  16. </props>
  17. </property>
  18. </bean>

自動代理BeanNameAutoProxyCreator:

Xml程式碼 複製程式碼
  1. <beanclass="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  2. <propertyname="beanNames">
  3. <!-- 所有以BUSImpl命名的Bean-Id都會被事務攔截-->
  4. <value>*BUSImpl</value>
  5. </property>
  6. <propertyname="interceptorNames">
  7. <list>
  8. <value>transactionInterceptor</value>
  9. </list>
  10. </property>
  11. </bean>

業務類例子:

Java程式碼 複製程式碼
  1. publicclass UserManageBUSImpl implements IBusiness{    
  2. private UserDAO dao;    
  3. publicvoid addUser(User user) throws Exception{    
  4.         dao.save(user);    
  5.     }    
  6. }   
Java程式碼 複製程式碼
  1. publicclass UserDAO implements IDAO{    
  2. private JdbcTemplate db;   
  3. publicvoid save(User user) throws Exception{    
  4.         db.update("insert into User(...) values(...)");    
  5. thrownew Exception("test exception"); // 這裡我們故意丟擲異常作為測試 
  6.     }    
  7. }   

   
 然後執行發現記錄仍然儲存進去了,事務失效;

why?

我們首先應該知道使用事務回滾和提交,歸根結底是在JDBC裡完成的,這裡宣告事務攔截器僅是為JDK代理切入點攔截。而做事務提交和回滾是transactionManager完成的事。那麼斷點跟進攔截器里程序發現:

Java程式碼 複製程式碼
  1. public Object invoke(final MethodInvocation invocation) throws Throwable {   
  2. // Work out the target class: may be <code>null</code>. 
  3. // The TransactionAttributeSource should be passed the target class 
  4. // as well as the method, which may be from an interface. 
  5.         Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null);   
  6. // If the transaction attribute is null, the method is non-transactional. 
  7. final TransactionAttribute txAttr =   
  8.                 getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);   
  9. final String joinpointIdentification = methodIdentification(invocation.getMethod());   
  10. if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {   
  11. // Standard transaction demarcation with getTransaction and commit/rollback calls. 
  12.             TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);   
  13.             Object retVal = null;   
  14. try {   
  15. // This is an around advice: Invoke the next interceptor in the chain. 
  16. // This will normally result in a target object being invoked. 
  17.                 retVal = invocation.proceed();   
  18.             }   
  19. catch (Throwable ex) {   
  20. // target invocation exception 
  21.                 <SPAN style="COLOR: #000000">completeTransactionAfterThrowing(txInfo, ex);</SPAN>   
  22. throw ex;   
  23.             }   
  24. finally {   
  25.                 cleanupTransactionInfo(txInfo);   
  26.             }   
  27.             commitTransactionAfterReturning(txInfo);   
  28. return retVal;   
  29.         }   
  30. ......  

completeTransactionAfterThrowing(txInfo, ex);這句話是異常捕獲後做的事情,那麼再跟進發現:

Java程式碼 複製程式碼
  1. protectedvoid completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {   
  2. if (txInfo != null && txInfo.hasTransaction()) {   
  3. if (logger.isDebugEnabled()) {    
  4.         logger.debug("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);    
  5.     }   
  6.     <SPAN style="COLOR: #ff0000">if (txInfo.transactionAttribute.rollbackOn(ex)) { // 需滿足這個條件</SPAN>  
  7. try {    
  8.             <SPAN style="COLOR: #ff0000">this.transactionManager.rollback(txInfo.getTransactionStatus()); // 這裡才完成JDBC事務回滾</SPAN>  
  9.         } catch (RuntimeException ex2) {    
  10.             logger.error("Application exception overridden by rollback exception", ex);    
  11. throw ex2;    
  12.         } catch (Error err) {    
  13.             logger.error("Application exception overridden by rollback error", ex); throw err; }    
  14.     }    
  15. ......   

 看來離真相越來越接近了,txInfo.transactionAttribute是什麼呢?檢視原始碼對應到一個介面TransactionAttribute,文件如下:

Java程式碼 複製程式碼
  1. publicinterface TransactionAttribute extends TransactionDefinition {   
  2. boolean rollbackOn(Throwable ex);   
  3. }  

 看下RuleBasedTransactionAttribute裡實現的介面方法:

Java程式碼 複製程式碼
  1. publicboolean rollbackOn(Throwable ex) {    
  2. if (logger.isDebugEnabled()) {   
  3.         logger.debug("Applying rules to determine whether transaction should rollback on " + ex);   
  4.     }    
  5.     RollbackRuleAttribute winner = null;    
  6. int deepest = Integer.MAX_VALUE;    
  7. if (this.rollbackRules != null) {    
  8.         <SPAN style="COLOR: #ff0000">// 看來這裡是要滿足自定義回滾規則  
  9. for (Iterator it = this.rollbackRules.iterator(); it.hasNext();) {    
  10. </SPAN>         RollbackRuleAttribute rule = (RollbackRuleAttribute) it.next();   
  11. int depth = rule.getDepth(ex);    
  12. if (depth >= 0 && depth < deepest) {   
  13.                 deepest = depth; winner = rule;   
  14.             }   
  15.         }    
  16.     }   
  17. if (logger.isDebugEnabled()) {   
  18.          logger.debug("Winning rollback rule is: " + winner);    
  19.     }    
  20. // User superclass behavior (rollback on unchecked) if no rule matches.  
  21. if (winner == null) {    
  22.         logger.debug("No relevant rollback rule found: applying superclass default");    
  23.         <SPAN style="COLOR: #ff0000">returnsuper.rollbackOn(ex); // 如果沒有規則,則呼叫父類方法驗證回滾規則</SPAN>  
  24.     }   
  25. return !(winner instanceof NoRollbackRuleAttribute); }   

 其父類方法為:

Java程式碼 複製程式碼
  1. publicboolean rollbackOn(Throwable ex) {    
  2. <SPAN style="COLOR: #ff0000"><SPAN style="COLOR: #000000">  </SPAN>return (ex instanceof RuntimeException || ex instanceof Error); // 最終是這個原因</SPAN>  
  3. }   

原因:

由於業務類裡丟擲的異常不滿足事務攔截器裡定義的異常(RuntimeException|Error)事務回滾規則,故事務無效;

解決方案:

1,將業務類的丟擲異常改為滿足攔截器裡的異常規則(不推薦,因為要修改以前所有的程式碼)

2,(推薦方案)在事務攔截器裡宣告自定義回滾規則,即this.rollbackRules.iterator()中有你自己申明的異常類,這個方案僅需在spring中配置如下:

Xml程式碼 複製程式碼
  1. <beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
  2. <propertyname="transactionManager"ref="transactionManager"/>
  3. <propertyname="transactionAttributes">
  4. <props>
  5. <propkey="add*">PROPAGATION_REQUIRED, -Exception</prop>
  6. <propkey="del*">PROPAGATION_REQUIRED, -Exception</prop>
  7. <propkey="update*">PROPAGATION_REQUIRED, -Exception</prop>
  8. <propkey="query*">readOnly</prop>
  9. <propkey="get*">readOnly</prop>
  10. <propkey="find*">readOnly</prop>
  11. <propkey="check*">PROPAGATION_REQUIRED, -Exception</prop>
  12. <propkey="operate*">PROPAGATION_REQUIRED, -Exception</prop>
  13. <propkey=