解決Spring AOP 事務 配置 失效原因--業務類裡丟擲的異常不滿足事務攔截器裡定義的異常
阿新 • • 發佈:2019-02-07
採用AOP配置宣告式事務有5種方式,下面只說關於採用TransactionInterceptor事務攔截器的方式,配置程式如下:
transactionManager:
Xml程式碼- <beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"/>
-
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <propertyname="dataSource"ref="dataSource"/>
- </bean>
TransactionInterceptor:
Xml程式碼- <beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
- <propertyname="transactionManager"ref="transactionManager"/>
-
<propertyname="transactionAttributes"
- <props>
- <propkey="add*">PROPAGATION_REQUIRED</prop>
- <propkey="del*">PROPAGATION_REQUIRED</prop>
- <propkey="update*">PROPAGATION_REQUIRED</prop>
- <propkey="query*">readOnly</prop>
- <propkey="get*">readOnly</prop>
-
<propkey="find*">readOnly
- <propkey="check*">PROPAGATION_REQUIRED</prop>
- <propkey="operate*">PROPAGATION_REQUIRED</prop>
- <propkey="batch*">PROPAGATION_REQUIRED</prop>
- <propkey="deploy*">PROPAGATION_REQUIRED</prop>
- <propkey="exec*">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
- </bean>
自動代理BeanNameAutoProxyCreator:
Xml程式碼- <beanclass="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <propertyname="beanNames">
- <!-- 所有以BUSImpl命名的Bean-Id都會被事務攔截-->
- <value>*BUSImpl</value>
- </property>
- <propertyname="interceptorNames">
- <list>
- <value>transactionInterceptor</value>
- </list>
- </property>
- </bean>
業務類例子:
Java程式碼- publicclass UserManageBUSImpl implements IBusiness{
- private UserDAO dao;
- publicvoid addUser(User user) throws Exception{
- dao.save(user);
- }
- }
- publicclass UserDAO implements IDAO{
- private JdbcTemplate db;
- publicvoid save(User user) throws Exception{
- db.update("insert into User(...) values(...)");
- thrownew Exception("test exception"); // 這裡我們故意丟擲異常作為測試
- }
- }
然後執行發現記錄仍然儲存進去了,事務失效;
why?
我們首先應該知道使用事務回滾和提交,歸根結底是在JDBC裡完成的,這裡宣告事務攔截器僅是為JDK代理切入點攔截。而做事務提交和回滾是transactionManager完成的事。那麼斷點跟進攔截器里程序發現:
Java程式碼- public Object invoke(final MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be <code>null</code>.
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface.
- Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null);
- // If the transaction attribute is null, the method is non-transactional.
- final TransactionAttribute txAttr =
- getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
- final String joinpointIdentification = methodIdentification(invocation.getMethod());
- if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {
- // Standard transaction demarcation with getTransaction and commit/rollback calls.
- TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);
- Object retVal = null;
- try {
- // This is an around advice: Invoke the next interceptor in the chain.
- // This will normally result in a target object being invoked.
- retVal = invocation.proceed();
- }
- catch (Throwable ex) {
- // target invocation exception
- <SPAN style="COLOR: #000000">completeTransactionAfterThrowing(txInfo, ex);</SPAN>
- throw ex;
- }
- finally {
- cleanupTransactionInfo(txInfo);
- }
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
- ......
completeTransactionAfterThrowing(txInfo, ex);這句話是異常捕獲後做的事情,那麼再跟進發現:
Java程式碼- protectedvoid completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
- if (txInfo != null && txInfo.hasTransaction()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);
- }
- <SPAN style="COLOR: #ff0000">if (txInfo.transactionAttribute.rollbackOn(ex)) { // 需滿足這個條件</SPAN>
- try {
- <SPAN style="COLOR: #ff0000">this.transactionManager.rollback(txInfo.getTransactionStatus()); // 這裡才完成JDBC事務回滾</SPAN>
- } catch (RuntimeException ex2) {
- logger.error("Application exception overridden by rollback exception", ex);
- throw ex2;
- } catch (Error err) {
- logger.error("Application exception overridden by rollback error", ex); throw err; }
- }
- ......
看來離真相越來越接近了,txInfo.transactionAttribute是什麼呢?檢視原始碼對應到一個介面TransactionAttribute,文件如下:
Java程式碼- publicinterface TransactionAttribute extends TransactionDefinition {
- boolean rollbackOn(Throwable ex);
- }
看下RuleBasedTransactionAttribute裡實現的介面方法:
Java程式碼- publicboolean rollbackOn(Throwable ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Applying rules to determine whether transaction should rollback on " + ex);
- }
- RollbackRuleAttribute winner = null;
- int deepest = Integer.MAX_VALUE;
- if (this.rollbackRules != null) {
- <SPAN style="COLOR: #ff0000">// 看來這裡是要滿足自定義回滾規則
- for (Iterator it = this.rollbackRules.iterator(); it.hasNext();) {
- </SPAN> RollbackRuleAttribute rule = (RollbackRuleAttribute) it.next();
- int depth = rule.getDepth(ex);
- if (depth >= 0 && depth < deepest) {
- deepest = depth; winner = rule;
- }
- }
- }
- if (logger.isDebugEnabled()) {
- logger.debug("Winning rollback rule is: " + winner);
- }
- // User superclass behavior (rollback on unchecked) if no rule matches.
- if (winner == null) {
- logger.debug("No relevant rollback rule found: applying superclass default");
- <SPAN style="COLOR: #ff0000">returnsuper.rollbackOn(ex); // 如果沒有規則,則呼叫父類方法驗證回滾規則</SPAN>
- }
- return !(winner instanceof NoRollbackRuleAttribute); }
其父類方法為:
Java程式碼- publicboolean rollbackOn(Throwable ex) {
- <SPAN style="COLOR: #ff0000"><SPAN style="COLOR: #000000"> </SPAN>return (ex instanceof RuntimeException || ex instanceof Error); // 最終是這個原因</SPAN>
- }
原因:
由於業務類裡丟擲的異常不滿足事務攔截器裡定義的異常(RuntimeException|Error)事務回滾規則,故事務無效;
解決方案:
1,將業務類的丟擲異常改為滿足攔截器裡的異常規則(不推薦,因為要修改以前所有的程式碼)
2,(推薦方案)在事務攔截器裡宣告自定義回滾規則,即this.rollbackRules.iterator()中有你自己申明的異常類,這個方案僅需在spring中配置如下:
Xml程式碼- <beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
- <propertyname="transactionManager"ref="transactionManager"/>
- <propertyname="transactionAttributes">
- <props>
- <propkey="add*">PROPAGATION_REQUIRED, -Exception</prop>
- <propkey="del*">PROPAGATION_REQUIRED, -Exception</prop>
- <propkey="update*">PROPAGATION_REQUIRED, -Exception</prop>
- <propkey="query*">readOnly</prop>
- <propkey="get*">readOnly</prop>
- <propkey="find*">readOnly</prop>
- <propkey="check*">PROPAGATION_REQUIRED, -Exception</prop>
- <propkey="operate*">PROPAGATION_REQUIRED, -Exception</prop>
- <propkey=