Springboot源碼分析之事務問題

摘要:

事務在後端開發中無處不在,是數據一致性的最基本保證。要明白進事務的本質就是進到事務切面的代理方法中,最多見的是同一個類的非事務方法調用一個加了事務註解的方法沒進入事務。咱們以cglib代理爲例,因爲Spring的對於cglib AOP代理的實現,進入被代理方法的時候實際上已經離開了「代理這一層殼子」,能夠認爲代碼走到的是一個樸素的bean,調用同一個bean中方法天然與代理沒有半毛錢關係了。 通常對於聲明式事務都是以調用另外一個類的加了@Transactional註解的public方法做爲入口的。java

spring事務關鍵處理流程

  • EnableTransactionManagement註解導入TransactionManagementConfigurationSelector
  • TransactionManagementConfigurationSelector加載InfrastructureAdvisorAutoProxyCreator(但不必定是它,通常都是AnnotationAwareAspectJAutoProxyCreator),BeanFactoryTransactionAttributeSourceAdvisorTransactionInterceptor - AnnotationAwareAspectJAutoProxyCreatorioc流程一個關鍵步驟是查找Advisor,有兩個方面,第一是實現了Advisor接口的類,第二是基於註解Aspectj。關鍵是BeanFactoryTransactionAttributeSourceAdvisor被加載進了代理緩存
  • 代理調用方法的時候會執行DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice,這個時候就會將咱們的

BeanFactoryTransactionAttributeSourceAdvisor派上用處,最主要的仍是它裏面的TransactionAttributeSourcePointcut進行匹配,執行TransactionInterceptor的方法spring

TransactionInterceptor

@Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
       // Work out the target class: may be {@code null}.
       // The TransactionAttributeSource should be passed the target class
       // as well as the method, which may be from an interface.
       Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    
       // Adapt to TransactionAspectSupport's invokeWithinTransaction...
       return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

TransactionAspectSupport

@Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
          final InvocationCallback invocation) throws Throwable {
    
       // If the transaction attribute is null, the method is non-transactional.
       TransactionAttributeSource tas = getTransactionAttributeSource();
       final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
       final PlatformTransactionManager tm = determineTransactionManager(txAttr);
       final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    
       if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
          // Standard transaction demarcation with getTransaction and commit/rollback calls.
          TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
          Object retVal;
          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.proceedWithInvocation();
          }
          catch (Throwable ex) {
             // target invocation exception
             completeTransactionAfterThrowing(txInfo, ex);
             throw ex;
          }
          finally {
             cleanupTransactionInfo(txInfo);
          }
          commitTransactionAfterReturning(txInfo);
          return retVal;
       }
    
       else {
          final ThrowableHolder throwableHolder = new ThrowableHolder();
    
          // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
          try {
             Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                   return invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                   if (txAttr.rollbackOn(ex)) {
                      // A RuntimeException: will lead to a rollback.
                      if (ex instanceof RuntimeException) {
                         throw (RuntimeException) ex;
                      }
                      else {
                         throw new ThrowableHolderException(ex);
                      }
                   }
                   else {
                      // A normal return value: will lead to a commit.
                      throwableHolder.throwable = ex;
                      return null;
                   }
                }
                finally {
                   cleanupTransactionInfo(txInfo);
                }
             });
    
             // Check result state: It might indicate a Throwable to rethrow.
             if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
             }
             return result;
          }
          catch (ThrowableHolderException ex) {
             throw ex.getCause();
          }
          catch (TransactionSystemException ex2) {
             if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                ex2.initApplicationException(throwableHolder.throwable);
             }
             throw ex2;
          }
          catch (Throwable ex2) {
             if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
             }
             throw ex2;
          }
       }
    }

此次在分析這個方法,可是從事務的異常,不生效等角度來分析問題。註解事務和編程式都同樣的核心思想,下面咱們來分析註解事務邏輯編程

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    			// Standard transaction demarcation with getTransaction and commit/rollback calls.
    			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
    			Object retVal;
    			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.proceedWithInvocation();
    			}
    			catch (Throwable ex) {
    				// target invocation exception
    				completeTransactionAfterThrowing(txInfo, ex);
    				throw ex;
    			}
    			finally {
            // 把上一層事務的TxInfo從新綁到ThreadLocal中
    				cleanupTransactionInfo(txInfo);
    			}
    			commitTransactionAfterReturning(txInfo);
    			return retVal;
    		}

請記住這幾個核心的方法邏輯順序和異常捕獲哦!後端

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    		if (txInfo != null && txInfo.getTransactionStatus() != null) {
    			if (logger.isTraceEnabled()) {
    				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
    						"] after exception: " + ex);
    			}
          //事務回滾的異常支持
    			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
    				try {
    					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
    				}
    				catch (TransactionSystemException ex2) {
    					logger.error("Application exception overridden by rollback exception", ex);
    					ex2.initApplicationException(ex);
    					throw ex2;
    				}
    				catch (RuntimeException | Error ex2) {
    					logger.error("Application exception overridden by rollback exception", ex);
    					throw ex2;
    				}
    			}
    			else {
    				// We don't roll back on this exception.
    				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
    				try {
    					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    				}
    				catch (TransactionSystemException ex2) {
    					logger.error("Application exception overridden by commit exception", ex);
    					ex2.initApplicationException(ex);
    					throw ex2;
    				}
    				catch (RuntimeException | Error ex2) {
    					logger.error("Application exception overridden by commit exception", ex);
    					throw ex2;
    				}
    			}
    		}
    	}

事務回滾的異常支持

@Override
    public boolean rollbackOn(Throwable ex) {
       return (ex instanceof RuntimeException || ex instanceof Error);
    }

注意點來了,僅支持運行時異常和錯誤機制,不然不予回滾。並進行直接條件。緩存

AbstractPlatformTransactionManager

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
       try {
         //默認false
          boolean unexpectedRollback = unexpected;
          try {
            //回調TransactionSynchronization對象的beforeCompletion方法。
             triggerBeforeCompletion(status);
             if (status.hasSavepoint()) {
                if (status.isDebug()) {
                   logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
             }
            // 在最外層事務邊界進行回滾
             else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                   logger.debug("Initiating transaction rollback");
                }
               // 由具體TxMgr子類實現回滾。
                doRollback(status);
             }
             else {
                // Participating in larger transaction
                if (status.hasTransaction()) {
                  /*
                     * 內層事務被標記爲rollBackOnly或者globalRollbackOnParticipationFailure開關開啓時,給當前事務標記須要回滾。
                     * 
                     * 若是內層事務顯式打上了rollBackOnly的標記,最終全事務必定是回滾掉的。
                     * 
                     * 但若是沒有被打上rollBackOnly標記,則globalRollbackOnParticipationFailure開關就很重要了。
                     * globalRollbackOnParticipationFailure開關默認是開啓的,也就是說內層事務掛了,最終的結果只能是全事務回滾。
                     * 但若是globalRollbackOnParticipationFailure開關被關閉的話,內層事務掛了,外層事務業務方法中能夠根據狀況控制是否回滾。
                     */
     
                   if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                      if (status.isDebug()) {
                         logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                      }
                     // 由具體TxMgr子類實現回滾。
                      doSetRollbackOnly(status);
                   }
                   else {
                      if (status.isDebug()) {
                         logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                      }
                   }
                }
                else {
                   logger.debug("Should roll back transaction but cannot - no transaction available");
                }
                // Unexpected rollback only matters here if we're asked to fail early
                if (!isFailEarlyOnGlobalRollbackOnly()) {
                   unexpectedRollback = false;
                }
             }
          }
          catch (RuntimeException | Error ex) {
             triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
             throw ex;
          }
    // 回調TransactionSynchronization對象的afterCompletion方法。
          triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    
          // Raise UnexpectedRollbackException if we had a global rollback-only marker
          if (unexpectedRollback) {
             throw new UnexpectedRollbackException(
                   "Transaction rolled back because it has been marked as rollback-only");
          }
       }
       finally {
          cleanupAfterCompletion(status);
       }
    }

案例分析

file

file

有經驗的同窗確定知道整個事務最終被回滾掉了, TransactionB#test並無執行System.out.println("TransactionB#test after"); 其實對於Spring事務來講,這樣的結果是正確的,但對於開發者來講,這個結果確實看似有些「不能理解」。ide

咱們不妨來分析一下緣由:

首先TransactionB#test自己是直接拋出RuntimeException的,那麼退棧到事務切面後,事務切面會發現須要回滾但由於TransactionB#test還不是事務的最外層邊界,因此在AbstractPlatformTransactionManager#processRollback方法僅僅會調用doSetRollbackOnly(status);,子類DataSourceTransactionManager會拿出DefaultTransactionStatus中的transaction對象打上回滾標記,具體來講就是transaction對象(對於DataSourceTransactionManager來講類型是DataSourceTransactionObject)會取出ConnectionHolder,調用setRollbackOnly。咱們知道這樣就至關於標記是一個全局的標記了,由於只要是隸屬於同一個物理事務的Spring事務都可以讀到同一個ConnectionHolderthis

protected void doSetRollbackOnly(DefaultTransactionStatus status) {
            DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
            if (status.isDebug()) {
                this.logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only");
            }
      //關鍵點
            txObject.setRollbackOnly();
        }

回到上層事務切面,在AbstractPlatformTransactionManager#commit方法讀到if(!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly())條件成立,接下來調用processRollback,因爲在事務最外層邊界會物理回滾掉,而且也正是到了事務最外層邊界,Spring拋出UnexpectedRollbackExceptiondebug

如何解決?

那麼問題怎麼解決呢,這個問題有好幾種解決辦法,可是得根據具體狀況決定。代理

  • 根據實際代碼與業務狀況處理,若是內嵌事務註解取消,Spring也不會拋出UnexpectedRollbackException。可是方法實際上並無完整執行,因此這樣的解決思路很容易致使出現不完整的髒數據。code

  • 手動控制是否回滾。若是不能接受內嵌事務掛掉的話,能夠在catch塊里加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();用於顯式控制回滾。這樣Spring就明白你本身要求回滾事務,而不是unexpected了。Spring也不會拋出UnexpectedRollbackException了。那麼若是在上層事務中捕獲到異常,真的就是不想回滾,即使上層事務發生了異常,也想要最終提交整個事務呢?若是有這樣的需求的話,能夠給事務管理器配置一個參數setGlobalRollbackOnParticipationFailure(false);

  • 若是isGlobalRollbackOnParticipationFailure爲false,則會讓主事務決定回滾,若是當遇到exception加入事務失敗時,調用者能繼續在事務內決定是回滾仍是繼續。然而,要注意是那樣作僅僅適用於在數據訪問失敗的狀況下且只要全部操做事務能提交,這個方法也能解決,但顯然影響到全局的事務屬性,因此極力不推薦使用。

public final void commit(TransactionStatus status) throws TransactionException {
       if (status.isCompleted()) {
          throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
       }
    
       DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
       if (defStatus.isLocalRollbackOnly()) {
          if (defStatus.isDebug()) {
             logger.debug("Transactional code has requested rollback");
          }
          processRollback(defStatus, false);
          return;
       }
    
       if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
          if (defStatus.isDebug()) {
             logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
          }
          processRollback(defStatus, true);
          return;
       }
    
       processCommit(defStatus);
    }
相關文章
相關標籤/搜索