Spring源碼解析——事務的回滾和提交

来源:https://www.cnblogs.com/tyson03/archive/2023/10/18/17773371.html
-Advertisement-
Play Games

正文 上一篇文章講解了獲取事務,並且通過獲取的connection設置只讀、隔離級別等,這篇文章講解剩下的事務的回滾和提交。最全面的Java面試網站 回滾處理 之前已經完成了目標方法運行前的事務準備工作,而這些準備工作最大的目的無非是對於程式沒有按照我們期待的那樣進行,也就是出現特定的錯誤,那麼,當 ...


正文

上一篇文章講解了獲取事務,並且通過獲取的connection設置只讀、隔離級別等,這篇文章講解剩下的事務的回滾和提交。最全面的Java面試網站

回滾處理

之前已經完成了目標方法運行前的事務準備工作,而這些準備工作最大的目的無非是對於程式沒有按照我們期待的那樣進行,也就是出現特定的錯誤,那麼,當出現錯誤的時候,Spring是怎麼對數據進行恢復的呢?

 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);
         }
         // 這裡判斷是否回滾預設的依據是拋出的異常是否是RuntimeException或者是Error的類型
         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;
             }
         }
     }
 }

在對目標方法的執行過程中,一旦出現Throwable就會被引導至此方法處理,但是並不代表所有的Throwable都會被回滾處理,比如我們最常用的Exception,預設是不會被處理的。 預設情況下,即使出現異常,數據也會被正常提交,而這個關鍵的地方就是在txlnfo.transactionAttribute.rollbackOn(ex)這個函數。

回滾條件

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

預設情況下Spring中的亊務異常處理機制只對RuntimeException和Error兩種情況感興趣,我們可以利用註解方式來改變,例如:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

回滾處理

當然,一旦符合回滾條件,那麼Spring就會將程式引導至回滾處理函數中。

分享一份大彬精心整理的大廠面試手冊,包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等高頻面試題,非常實用,有小伙伴靠著這份手冊拿過位元組offer~

需要的小伙伴可以自行下載

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;

        try {
            triggerBeforeCompletion(status);
            // 如果status有savePoint,說明此事務是NESTD,且為子事務,只回滾到savePoint
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                //回滾到保存點
                status.rollbackToHeldSavepoint();
            }
            // 如果此時的status顯示是新的事務才進行回滾
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                //如果此時是子事務,我們想想哪些類型的事務會進入到這裡?
                //回顧上一篇文章中已存在事務的處理,NOT_SUPPORTED創建的Status是prepareTransactionStatus(definition, null, false...),說明是舊事物,並且事務為null,不會進入
                //REQUIRES_NEW會創建一個新的子事務,Status是newTransactionStatus(definition, transaction, true...)說明是新事務,將會進入到這個分支
                //PROPAGATION_NESTED創建的Status是prepareTransactionStatus(definition, transaction, false...)是舊事物,使用的是外層的事務,不會進入
                //PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY存在事務加入事務即可,標記為舊事務,prepareTransactionStatus(definition, transaction, false..)
                //說明當子事務,只有REQUIRES_NEW會進入到這裡進行回滾
                doRollback(status);
            }
            else {
                // Participating in larger transaction
                // 如果status中有事務,進入下麵
                // 根據上面分析,PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY創建的Status是prepareTransactionStatus(definition, transaction, false..)
                // 如果此事務時子事務,表示存在事務,並且事務為舊事物,將進入到這裡
                if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        // 對status中的transaction作一個回滾了的標記,並不會立即回滾
                        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;
        }

        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

    }
    finally {
        // 清空記錄的資源並將掛起的資源恢復
        // 子事務結束了,之前掛起的事務就要恢復了
        cleanupAfterCompletion(status);
    }
}

我i們先來看看第13行,回滾到保存點的代碼,根據保存點回滾的實現方式其實是根據底層的資料庫連接進行的。回滾到保存點之後,也要釋放掉當前的保存點

public void rollbackToHeldSavepoint() throws TransactionException {
    Object savepoint = getSavepoint();
    if (savepoint == null) {
        throw new TransactionUsageException(
                "Cannot roll back to savepoint - no savepoint associated with current transaction");
    }
    getSavepointManager().rollbackToSavepoint(savepoint);
    getSavepointManager().releaseSavepoint(savepoint);
    setSavepoint(null);
}

這裡使用的是JDBC的方式進行資料庫連接,那麼getSavepointManager()函數返回的是JdbcTransactionObjectSupport,也就是說上面函數會調用JdbcTransactionObjectSupport 中的 rollbackToSavepoint 方法。

@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
    ConnectionHolder conHolder = getConnectionHolderForSavepoint();
    try {
        conHolder.getConnection().rollback((Savepoint) savepoint);
        conHolder.resetRollbackOnly();
    }
    catch (Throwable ex) {
        throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
    }
}

當之前已經保存的事務信息中的事務為新事物,那麼直接回滾。常用於單獨事務的處理。對於沒有保存點的回滾,Spring同樣是使用底層資料庫連接提供的API來操作的。由於我們使用的是DataSourceTransactionManager,那麼doRollback函數會使用此類中的實現:

@Override
protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) {
        logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
    }
    try {
        con.rollback();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
    }
}

當前事務信息中表明是存在事務的,又不屬於以上兩種情況,只做回滾標識,等到提交的時候再判斷是否有回滾標識,下麵回滾的時候再介紹,子事務中狀態為PROPAGATION_SUPPORTSPROPAGATION_REQUIREDPROPAGATION_MANDATORY回滾的時候將會標記為回滾標識,我們來看看是怎麼標記的

@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
    // 將status中的transaction取出
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    if (status.isDebug()) {
    logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
    "] rollback-only");
    }
    // transaction執行標記回滾
    txObject.setRollbackOnly();
}
public void setRollbackOnly() {
    // 這裡將transaction裡面的connHolder標記回滾
    getConnectionHolder().setRollbackOnly();
}
public void setRollbackOnly() {
    // 將holder中的這個屬性設置成true
    this.rollbackOnly = true;
}

我們看到將status中的Transaction中的 ConnectionHolder的屬性rollbackOnly標記為true,這裡我們先不多考慮,等到下麵提交的時候再介紹

我們簡單的做個小結

  • status.hasSavepoint()如果status中有savePoint,只回滾到savePoint!
  • status.isNewTransaction()如果status是一個新事務,才會真正去回滾!
  • status.hasTransaction()如果status有事務,將會對staus中的事務標記!

事務提交

在事務的執行並沒有出現任何的異常,也就意味著事務可以走正常事務提交的流程了。這裡回到流程中去,看看commitTransactionAfterReturning(txInfo)方法做了什麼:

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

在真正的數據提交之前,還需要做個判斷。不知道大家還有沒有印象,在我們分析事務異常處理規則的時候,當某個事務既沒有保存點又不是新事物,Spring對它的處理方式只是設置一個回滾標識。這個回滾標識在這裡就會派上用場了,如果子事務狀態是

PROPAGATION_SUPPORTSPROPAGATION_REQUIREDPROPAGATION_MANDATORY,將會在外層事務中運行,回滾的時候,並不執行回滾,只是標記一下回滾狀態,當外層事務提交的時候,會先判斷ConnectionHolder中的回滾狀態,如果已經標記為回滾,則不會提交,而是外層事務進行回滾

@Override
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);
 }

而當事務執行一切都正常的時候,便可以真正地進入提交流程了。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;
            prepareForCommit(status);
            triggerBeforeCommit(status);
            triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;
            
            // 判斷是否有savePoint
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Releasing transaction savepoint");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                // 不提交,僅僅是釋放savePoint
                status.releaseHeldSavepoint();
            }
            // 判斷是否是新事務
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction commit");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                // 這裡才真正去提交!
                doCommit(status);
            }
            else if (isFailEarlyOnGlobalRollbackOnly()) {
                unexpectedRollback = status.isGlobalRollbackOnly();
            }

            // Throw UnexpectedRollbackException if we have a global rollback-only
            // marker but still didn't get a corresponding exception from commit.
            if (unexpectedRollback) {
                throw new UnexpectedRollbackException(
                    "Transaction silently rolled back because it has been marked as rollback-only");
            }
        }
        catch (UnexpectedRollbackException ex) {
            // 略...
        }

        // Trigger afterCommit callbacks, with an exception thrown there
        // propagated to callers but the transaction still considered as committed.
        try {
            triggerAfterCommit(status);
        }
        finally {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
        }

    }
    finally {
        // 清空記錄的資源並將掛起的資源恢復
        cleanupAfterCompletion(status);
    }
}
  • status.hasSavepoint()如果status有savePoint,說明此時的事務是嵌套事務NESTED,這個事務外面還有事務,這裡不提交,只是釋放保存點。這裡也可以看出來NESTED的傳播行為了。
  • status.isNewTransaction()如果是新的事務,才會提交!!,這裡如果是子事務,只有PROPAGATION_NESTED狀態才會走到這裡提交,也說明瞭此狀態子事務提交和外層事務是隔離的
  • 如果是子事務,PROPAGATION_SUPPORTSPROPAGATION_REQUIREDPROPAGATION_MANDATORY這幾種狀態是舊事物,提交的時候將什麼都不做,因為他們是運行在外層事務當中,如果子事務沒有回滾,將由外層事務一次性提交

如果程式流通過了事務的層層把關,最後順利地進入了提交流程,那麼同樣,Spring會將事務提交的操作引導至底層資料庫連接的API,進行事務提交。

@Override
protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) {
        logger.debug("Committing JDBC transaction on Connection [" + con + "]");
    }
    try {
        con.commit();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit JDBC transaction", ex);
    }
}

從回滾和提交的邏輯看,只有status是新事務,才會進行提交或回滾,需要讀者記好這個狀態–>是否是新事務。

清理工作

而無論是在異常還是沒有異常的流程中,最後的finally塊中都會執行一個方法cleanupAfterCompletion(status)

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    // 設置完成狀態
    status.setCompleted();
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction after completion of inner transaction");
        }
        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
        // 結束之前事務的掛起狀態
        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

如果是新事務需要做些清除資源的工作?

@Override
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // Remove the connection holder from the thread, if exposed.
    if (txObject.isNewConnectionHolder()) {
        // 將資料庫連接從當前線程中解除綁定,解綁過程我們在掛起的過程中已經分析過
        TransactionSynchronizationManager.unbindResource(obtainDataSource());
    }

    // Reset connection.
    // 釋放連接,當前事務完成,則需要將連接釋放,如果有線程池,則重置資料庫連接,放回線程池
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            // 恢複數據庫連接的自動提交屬性
            con.setAutoCommit(true);
        }
        // 重置資料庫連接
        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }

    if (txObject.isNewConnectionHolder()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
        }
        // 如果當前事務是獨立的新創建的事務則在事務完成時釋放資料庫連接
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }

    txObject.getConnectionHolder().clear();
}

如果在事務執行前有事務掛起,那麼當前事務執行結束後需要將掛起事務恢復。

如果有掛起的事務的話,status.getSuspendedResources() != null,也就是說status中會有suspendedResources這個屬性,取得status中的transaction後進入resume方法:

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
throws TransactionException {
    
    if (resourcesHolder != null) {
        Object suspendedResources = resourcesHolder.suspendedResources;
        // 如果有被掛起的事務才進入
        if (suspendedResources != null) {
            // 真正去resume恢復的地方
            doResume(transaction, suspendedResources);
        }
        List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
        if (suspendedSynchronizations != null) {
            // 將上面提到的TransactionSynchronizationManager專門存放線程變數的類中
            // 的屬性設置成被掛起事務的屬性
        TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
        TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
        doResumeSynchronization(suspendedSynchronizations);
        }
    }
}

我們來看看doResume

@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
    TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}

這裡恢復只是把suspendedResources重新綁定到線程中。

幾種事務傳播屬性詳解

我們先來看看七種傳播屬性

Spring事物傳播特性表:

傳播特性名稱 說明
PROPAGATION_REQUIRED 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中
PROPAGATION_SUPPORTS 支持當前事物,如果當前沒有事物,則以非事物方式執行
PROPAGATION_MANDATORY 使用當前事物,如果當前沒有事物,則拋出異常
PROPAGATION_REQUIRES_NEW 新建事物,如果當前已經存在事物,則掛起當前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式執行,如果當前存在事物,則掛起當前事物
PROPAGATION_NEVER 以非事物方式執行,如果當前存在事物,則拋出異常
PROPAGATION_NESTED 如果當前存在事物,則在嵌套事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同

當前不存在事務的情況下

每次創建一個TransactionInfo的時候都會去new一個transaction,然後去線程變數Map中拿holder,當此時線程變數的Map中holder為空時,就視為當前情況下不存在事務,所以此時transaction中holder = null。

1、PROPAGATION_MANDATORY

使用當前事物,如果當前沒有事物,則拋出異常

在上一篇博文中我們在getTransaction方法中可以看到如下代碼,當前線程不存在事務時,如果傳播屬性為PROPAGATION_MANDATORY,直接拋出異常,因為PROPAGATION_MANDATORY必須要在事務中運行

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
    throw new IllegalTransactionStateException(
            "No existing transaction found for transaction marked with propagation 'mandatory'");
}

2、REQUIRED、REQUIRES_NEW、NESTED

我們繼續看上一篇博文中的getTransaction方法

else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    // PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED都需要新建事務
    // 因為此時不存在事務,將null掛起
    SuspendedResourcesHolder suspendedResources = suspend(null);
    if (debugEnabled) {
        logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
    }
    try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        // new一個status,存放剛剛創建的transaction,然後將其標記為新事務!
        // 這裡transaction後面一個參數決定是否是新事務!
        DefaultTransactionStatus status = newTransactionStatus(
                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
        // 新開一個連接的地方,非常重要
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
    }
    catch (RuntimeException | Error ex) {
        resume(null, suspendedResources);
        throw ex;
    }
}

此時會講null掛起,此時的status變數為:

DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

此時的transaction中holder依然為null,標記為新事務,接著就會執行doBegin方法了:

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

        // 此時會進入這個if語句塊,因為此時的holder依然為null
        if (!txObject.hasConnectionHolder() ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction())                 {
            // 從dataSource從取得一個新的connection
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            // new一個新的holder放入新的連接,設置為新的holder
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        // 略...
        
        prepareTransactionalConnection(con, definition);
        // 將holder設置avtive = true
        txObject.getConnectionHolder().setTransactionActive(true);

        // Bind the connection holder to the thread.
        // 綁定到當前線程
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
        }
    }
}

所以,一切都是新的,新的事務,新的holder,新的連接,在當前不存在事務的時候一切都是新創建的。

這三種傳播特性在當前不存在事務的情況下是沒有區別的,此事務都為新創建的連接,在回滾和提交的時候都可以正常回滾或是提交,就像正常的事務操作那樣。

3、PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER

我們看看當傳播屬性為PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER這幾種時的代碼,getTransaction方法

else {
    //其他的傳播特性一律返回一個空事務,transaction = null
    //當前不存在事務,且傳播機制=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,這三種情況,創建“空”事務
    boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}

我們看到Status中第二個參數傳的是null,表示一個空事務,意思是當前線程中並沒有Connection,那如何進行資料庫的操作呢?上一篇文章中我們有一個擴充的知識點,Mybaits中使用的資料庫連接是從通過TransactionSynchronizationManager.getResource(Object key)獲取spring增強方法中綁定到線程的connection,如下代碼,那當傳播屬性為PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER這幾種時,並沒有創建新的Connection,當前線程中也沒有綁定Connection,那Mybatis是如何獲取Connecion的呢?這裡留一個疑問,我們後期看Mybatis的源碼的時候來解決這個疑問

@Nullable
public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doGetResource(actualKey);
    if (value != null && logger.isTraceEnabled()) {
        logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
                Thread.currentThread().getName() + "]");
    }
    return value;
}

    @Nullable
private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = resources.get();
    if (map == null) {
        return null;
    }
    Object value = map.get(actualKey);
    // Transparently remove ResourceHolder that was marked as void...
    if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
        map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

此時我們知道Status中的Transaction為null,在目標方法執行完畢後,進行回滾或提交的時候,會判斷當前事務是否是新事務,代碼如下

@Override
public boolean isNewTransaction() {
    return (hasTransaction() && this.newTransaction);
}

此時transacion為null,回滾或提交的時候將什麼也不做

當前存在事務情況下

上一篇文章中已經講過,第一次事務開始時必會新創一個holder然後做綁定操作,此時線程變數是有holder的且avtive為true,如果第二個事務進來,去new一個transaction之後去線程變數中取holder,holder是不為空的且active是為true的,所以會進入handleExistingTransaction方法:

 private TransactionStatus handleExistingTransaction(
         TransactionDefinition definition, Object transaction, boolean debugEnabled)
         throws TransactionException {
    // 1.NERVER(不支持當前事務;如果當前事務存在,拋出異常)報錯
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
         throw new IllegalTransactionStateException(
                 "Existing transaction found for transaction marked with propagation 'never'");
     }
    // 2.NOT_SUPPORTED(不支持當前事務,現有同步將被掛起)掛起當前事務,返回一個空事務
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
         if (debugEnabled) {
             logger.debug("Suspending current transaction");
         }
         // 這裡會將原來的事務掛起,並返回被掛起的對象
         Object suspendedResources = suspend(transaction);
         boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
         // 這裡可以看到,第二個參數transaction傳了一個空事務,第三個參數false為舊標記
         // 最後一個參數就是將前面掛起的對象封裝進新的Status中,當前事務執行完後,就恢復suspendedResources
         return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
     }
    // 3.REQUIRES_NEW掛起當前事務,創建新事務
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
         if (debugEnabled) {
             logger.debug("Suspending current transaction, creating new transaction with name [" +
                     definition.getName() + "]");
         }
         // 將原事務掛起,此時新建事務,不與原事務有關係
         // 會將transaction中的holder設置為null,然後解綁!
         SuspendedResourcesHolder suspendedResources = suspend(transaction);
         try {
             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
             // new一個status出來,傳入transaction,並且為新事務標記,然後傳入掛起事務
             DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
             // 這裡也做了一次doBegin,此時的transaction中holer是為空的,因為之前的事務被掛起了
             // 所以這裡會取一次新的連接,並且綁定!
             doBegin(transaction, definition);
             prepareSynchronization(status, definition);
             return status;
         }
         catch (RuntimeException beginEx) {
             resumeAfterBeginException(transaction, suspendedResources, beginEx);
             throw beginEx;
         }
         catch (Error beginErr) {
             resumeAfterBeginException(transaction, suspendedResources, beginErr);
             throw beginErr;
         }
     }
   // 如果此時的傳播特性是NESTED,不會掛起事務
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
         if (!isNestedTransactionAllowed()) {
             throw new NestedTransactionNotSupportedException(
                     "Transaction manager does not allow nested transactions by default - " +
                     "specify 'nestedTransactionAllowed' property with value 'true'");
         }
         if (debugEnabled) {
             logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
         }
         // 這裡如果是JTA事務管理器,就不可以用savePoint了,將不會進入此方法
         if (useSavepointForNestedTransaction()) { 
             // 這裡不會掛起事務,說明NESTED的特性是原事務的子事務而已
             // new一個status,傳入transaction,傳入舊事務標記,傳入掛起對象=null
             DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
             // 這裡是NESTED特性特殊的地方,在先前存在事務的情況下會建立一個savePoint
             status.createAndHoldSavepoint();
             return status;
         }
         else {
             // JTA事務走這個分支,創建新事務
             boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
             DefaultTransactionStatus status = newTransactionStatus(
                     definition, transaction, true, newSynchronization, debugEnabled, null);
             doBegin(transaction, definition);
             prepareSynchronization(status, definition);
             return status;
         }
     }
 
     // 到這裡PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,存在事務加入事務即可,標記為舊事務,空掛起
     boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
     return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
 }

1、NERVER

不支持當前事務;如果當前事務存在,拋出異常

// 1.NERVER(不支持當前事務;如果當前事務存在,拋出異常)報錯
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
    throw new IllegalTransactionStateException(
            "Existing transaction found for transaction marked with propagation 'never'");
}

我們看到如果當前線程中存在事務,傳播屬性為PROPAGATION_NEVER,會直接拋出異常

2、NOT_SUPPORTED

以非事物方式執行,如果當前存在事物,則掛起當前事物

我們看上面代碼第9行,如果傳播屬性為PROPAGATION_NOT_SUPPORTED,會先將原來的transaction掛起,此時status為:

return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);

transaction為空,舊事務,掛起的對象存入status中。

此時與外層事務隔離了,在這種傳播特性下,是不進行事務的,當提交時,因為是舊事務,所以不會commit,失敗時也不會回滾rollback

3、REQUIRES_NEW

此時會先掛起,然後去執行doBegin方法,此時會創建一個新連接,新holder,新holder有什麼用呢?

如果是新holder,會在doBegin中做綁定操作,將新holder綁定到當前線程,其次,在提交或是回滾時finally語句塊始終會執行清理方法時判斷新holder會進行解綁操作。

@Override
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // Remove the connection holder from the thread, if exposed.
    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(obtainDataSource());
    }
}

符合傳播特性,所以這裡REQUIRES_NEW這個傳播特性是與原事務相隔的,用的連接都是新new出來的。

此時返回的status是這樣的:

DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

其中transaction中holder為新holder,連接都是新的。標記為新事務,在開頭的回顧中提到,如果是新事務,提交時才能成功提交。並且在最後一個參數放入掛起的對象,之後將會恢復它。

REQUIRES_NEW小結

會於前一個事務隔離,自己新開一個事務,與上一個事務無關,如果報錯,上一個事務catch住異常,上一個事務是不會回滾的,這裡要註意(在invokeWithinTransaction方法中的catch代碼塊中,處理完異常後,還通過 throw ex;將異常拋給了上層,所以上層要catch住子事務的異常,子事務回滾後,上層事務也會回滾),而只要自己提交了之後,就算上一個事務後面的邏輯報錯,自己是不會回滾的(因為被標記為新事務,所以在提交階段已經提交了)。

4、NESTED

不掛起事務,並且返回的status對象如下:

DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);

status.createAndHoldSavepoint();

不同於其他的就是此傳播特性會創建savePoint,有什麼用呢?前面說到,如果是舊事務的話回滾是不會執行的,但先看看它的status,雖然標記為舊事務,但它還有savePoint,如果有savePoint,會回滾到保存點去,提交的時候,會釋放保存點,但是不提交!切記,這裡就是NESTED與REQUIRES_NEW不同點之一了,NESTED只會在外層事務成功時才進行提交,實際提交點只是去釋放保存點,外層事務失敗,NESTED也將回滾,但如果是REQUIRES_NEW的話,不管外層事務是否成功,它都會提交不回滾。這就是savePoint的作用。

由於不掛起事務,可以看出來,此時transaction中的holder用的還是舊的,連接也是上一個事務的連接,可以看出來,這個傳播特性會將原事務和自己當成一個事務來做。

NESTED 小結

與前一個事務不隔離,沒有新開事務,用的也是老transaction,老的holder,同樣也是老的connection,沒有掛起的事務。關鍵點在這個傳播特性在存在事務情況下會創建savePoint,但不存在事務情況下是不會創建savePoint的。在提交時不真正提交,只是釋放了保存點而已,在回滾時會回滾到保存點位置,如果上層事務catch住異常的話,是不會影響上層事務的提交的,外層事務提交時,會統一提交,外層事務回滾的話,會全部回滾

5、REQUIRED 、PROPAGATION_REQUIRED或PROPAGATION_MANDATORY

存在事務加入事務即可,標記為舊事務,空掛起

status為:

return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

使用舊事務,標記為舊事務,掛起對象為空。

與前一個事務不隔離,沒有新開事務,用的也是老transaction,老的connection,但此時被標記成舊事務,所以,在提交階段不會真正提交的,在外層事務提交階段,才會把事務提交。

如果此時這裡出現了異常,內層事務執行回滾時,舊事務是不會去回滾的,而是進行回滾標記,我們看看文章開頭處回滾的處理函數processRollback中第39行,當前事務信息中表明是存在事務的,但是既沒有保存點,又不是新事務,回滾的時候只做回滾標識,等到提交的時候再判斷是否有回滾標識,commit的時候,如果有回滾標識,就進行回滾

@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
    // 將status中的transaction取出
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    if (status.isDebug()) {
    logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
    "] rollback-only");
    }
    // transaction執行標記回滾
    txObject.setRollbackOnly();
}
public void setRollbackOnly() {
    // 這裡將transaction裡面的connHolder標記回滾
    getConnectionHolder().setRollbackOnly();
}
public void setRollbackOnly() {
    // 將holder中的這個屬性設置成true
    this.rollbackOnly = true;
}

我們知道,在內層事務中transaction對象中的holder對象其實就是外層事務transaction里的holder,holder是一個對象,指向同一個地址,在這裡設置holder標記,外層事務transaction中的holder也是會被設置到的,在外層事務提交的時候有這樣一段代碼:

@Override
public final void commit(TransactionStatus status) throws TransactionException {
    // 略...

    // !shouldCommitOnGlobalRollbackOnly()只有JTA與JPA事務管理器才會返回false
    // defStatus.isGlobalRollbackOnly()這裡判斷status是否被標記了
    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;
    }

    // 略...
}

在外層事務提交的時候是會去驗證transaction中的holder里是否被標記rollback了,內層事務回滾,將會標記holder,而holder是線程變數,在此傳播特性中holder是同一個對象,外層事務將無法正常提交而進入processRollback方法進行回滾,並拋出異常:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        // 此時這個值為true
        boolean unexpectedRollback = unexpected;

        try {
            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");
                }
                // 回滾!
                doRollback(status);
            }
            
         // 略...
            
        // 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);
    }
}

最後給大家分享200多本電腦經典書籍PDF電子書,包括C語言、C++、Java、Python、前端、資料庫、操作系統、電腦網路、數據結構和演算法、機器學習、編程人生等,感興趣的小伙伴可以自取:

https://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247486208&idx=1&sn=dbeedf47c50b1be67b2ef31a901b8b56&chksm=ce98f646f9ef7f506a1f7d72fc9384ba1b518072b44d157f657a8d5495a1c78c3e5de0b41efd&token=1652861108&lang=zh_CN#rd


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我的小冊 《CSS 技術揭秘與實戰通關》上線了,想瞭解更多有趣、進階、系統化的 CSS 內容,可以猛擊 - LINK。 最近大家刷抖音,是否有刷到拉斯維加斯的新地標 「Sphere」: 場館內部的視覺效果非常驚人,其中一個效果讓我虎軀一震: 我的第一想法就是,這個看起來用 CSS 也可以實現嘛?還有 ...
  • 本文總結了軟體開發過程中經常用到的基礎常識,分為基礎篇和實踐篇兩個篇章,其中基礎篇中著重講述了類,方法,變數的命名規範以及代碼註釋好壞的評判標準。實踐篇中從類,方法以及對象三個層面分析了常見的技術概念和落地實踐,希望這些常識能夠為讀者帶來一些思考和幫助。 ...
  • 架構的範疇太大太廣,本文嘗試從一個點切入闡述一下個人的認知。有太多相關性的問題想去闡述,比如SOA與BPM的結合、實踐過程中遇到的細節問題等等,為了比較乾凈的剖析SOA還是刪除掉了。希望各位看官有所收穫。 ...
  • 一、定義 組合多個對象形成樹形結構以表示具有部分-整體關係的層次結構。組合模式讓客戶端可以統一對待單個對象和組合對象。組合模式是一種結構型模式。 二、描述 包含以下三個角色:1、Component(抽象構件):它可以是介面或抽象類,為葉子構件和容器構件對象聲明介面,在該角色中可以包含所有子類共有行為 ...
  • 無論是在內部系統還是在外部的互聯網站上,都少不了檢索系統。數據是為了用戶而服務。電腦在採集數據,處理數據,存儲數據之後,各種客戶端的操作pc機或者是移動嵌入式設備都可以很好的獲取數據,得到 想要的數據服務。 檢索分為SQL過濾查詢和全文檢索。數據都是放在資料庫里,資料庫里的數據量太大,要檢索到精準 ...
  • 天帶來的是架構活動中的常見原則,在我們平時做技術方案,非功能設計時一定需要銘記於心這些方法論。 架構目標 高可用性 整體系統可用性最低99.9%,目標99.99%。全年故障時間整個系統不超過500分鐘,單個系統故障不超過50分鐘。 高可擴展性 系統架構簡單清晰,應用系統間耦合低,容易水平擴展,業務功 ...
  • 三子棋游戲一、分析 1.創建一個進入游戲讓玩家選擇的框架2.創建一個三子棋的棋盤,棋盤內部存放玩家和電腦下的棋子,所以總的來說棋盤是由一個二維數組和棋盤框架構成的3.對棋盤進行操作4.判斷棋局並做出調整 二、代碼 game.h #define _CRT_SECURE_NO_WARNINGS 1 #i ...
  • mybatis逆向工程,即利用現有的數據表結構,生成對應的model實體類、dao層介面,以及對應的mapper.xml映射文件。藉助mybatis逆向工程,我們無需手動去創建這些文件。 下麵是使用Java代碼的方式來實現逆向工程,生成文件(也可以使用插件來生成): 首先,導入需要的依賴包:myba ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...