Spring 采用保存點(Savepoint)實現(xiàn)嵌套事務原理

1 概述

在Spring事務中,我們可以配置事務的傳播屬性,傳播屬性的處理在函數(shù)AbstractPlatformTransactionManager.handleExistingTransaction中,具體可參考源碼。關于Spring中傳播屬性的定義可見其官方文檔Transaction Propagation,對于本文介紹的PROPAGATION_NESTED,Spring官方文檔描述如下:

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks let an inner transaction scope trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so it works only with JDBC resource transactions. See Spring’s DataSourceTransactionManager.

可見,Spring采用一個物理事務,但是結合著savepoint機制(MySql中稱為保存點)實現(xiàn)一個事務中的指定范圍提交。

2 保存點創(chuàng)建準備

Spring如何使用AOP實現(xiàn)事務控制的邏輯這里不去詳細介紹,我們通過源碼追蹤可以發(fā)現(xiàn)調用軌跡如下:
TransactionInterceptor.invoke->
TransactionAspectSupport.invokeWithinTransaction->
TransactionAspectSupport.createTransactionIfNecessary->
AbstractPlatformTransactionManager.getTransaction->
AbstractPlatformTransactionManager.doGetTransaction->

這里我們看AbstractPlatformTransactionManager子類DataSourceTransactionManager.doGetTransaction實現(xiàn)

繼續(xù)上面的過程:

//DataSourceTransactionManager
@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    //看下方列出的DataSourceTransactionManager構造函數(shù),
    //isNestedTransactionAllowed會返回true
    //就是默認支持嵌套事務
    //而嵌套事務又是采用savepoint實現(xiàn)的
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    ConnectionHolder conHolder =
            (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

/**
    * Create a new DataSourceTransactionManager instance.
    * A DataSource has to be set to be able to use it.
    * @see #setDataSource
    */
public DataSourceTransactionManager() {
    setNestedTransactionAllowed(true);
}

上面已經介紹了,如果支持嵌套事務,則創(chuàng)建的DataSourceTransactionObject.isSavepointAllowed會被設為true。

3 保存點創(chuàng)建

在第一章概述中說到,如果傳播屬性設為PROPAGATION_NESTED,如果創(chuàng)建事務時已經存在了一個事務,則會創(chuàng)建一個嵌套事務:

//AbstractPlatformTransactionManager
//省略其他不相關代碼
/**
    * Create a TransactionStatus for an existing transaction.
    */
private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    ...
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        //如果當前TransactionManager不支持嵌套事務
        //直接拋錯
        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() + "]");
        }
        //判斷當前TransactionManager實現(xiàn)是否是采用保存點實現(xiàn)嵌套事務
        if (useSavepointForNestedTransaction()) {
            // Create savepoint within existing Spring-managed transaction,
            // through the SavepointManager API implemented by TransactionStatus.
            // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
            DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
            //創(chuàng)建保存點
            status.createAndHoldSavepoint();
            return status;
        }
        else {
            //使用嵌套的begin、commit/rollback實現(xiàn)嵌套事務,MySql
            //不支持,因為如果已經調用過begin,提交之前再調用
            //begin操作,MySql會隱式調用一次commit,不能達到嵌套事務
            //的效果,這種方式某些數(shù)據(jù)庫可能會支持,這里不做介紹
            // Nested transaction through nested begin and commit/rollback calls.
            // Usually only for JTA: Spring synchronization might get activated here
            // in case of a pre-existing JTA transaction.
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, null);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
    }

    ...
}

上面關于MySql begin操作隱式進行提交可參考其官方描述Statements That Cause an Implicit Commit

DefaultTransactionStatus.createAndHoldSavepoint在其父類AbstractTransactionStatus實現(xiàn):

//AbstractTransactionStatus
//具體怎么創(chuàng)建的不再展開,可以自行查看代碼
/**
* Create a savepoint and hold it for the transaction.
* @throws org.springframework.transaction.NestedTransactionNotSupportedException
* if the underlying transaction does not support savepoints
*/
public void createAndHoldSavepoint() throws TransactionException {
    setSavepoint(getSavepointManager().createSavepoint());
}

DefaultTransactionStatus創(chuàng)建保存點之后同時會保存該保存點,也就是上面setSavepoint的調用。

4 保存點提交或釋放

4.1 保存點提交

在事務完成之后,會進行事務提交,具體的會調用AbstractPlatformTransactionManager.commit

//AbstractPlatformTransactionManager
/**
* This implementation of commit handles participating in existing
* transactions and programmatic rollback requests.
* Delegates to {@code isRollbackOnly}, {@code doCommit}
* and {@code rollback}.
* @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
* @see #doCommit
* @see #rollback
*/
@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);
}

我們先看正常提交processCommit:

//AbstractPlatformTransactionManager
/**
* Process an actual commit.
* Rollback-only flags have already been checked and applied.
* @param status object representing the transaction
* @throws TransactionException in case of commit failure
*/
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;
            prepareForCommit(status);
            triggerBeforeCommit(status);
            triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;
            //如果當前status有保存點,表示當前提交的是嵌套在某個事務
            //內的子事務,通過釋放保存點提交
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Releasing transaction savepoint");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                //釋放保存的保存點
                status.releaseHeldSavepoint();
            }//else表示通過begin、commit/rollback實現(xiàn)子事務,這里不介紹
            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) {
            // can only be caused by doCommit
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            throw ex;
        }
        catch (TransactionException ex) {
            // can only be caused by doCommit
            if (isRollbackOnCommitFailure()) {
                doRollbackOnCommitException(status, ex);
            }
            else {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            }
            throw ex;
        }
        catch (RuntimeException | Error ex) {
            if (!beforeCompletionInvoked) {
                triggerBeforeCompletion(status);
            }
            doRollbackOnCommitException(status, ex);
            throw 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);
    }
}

4.2 保存點釋放

發(fā)生異常時的回滾操作實現(xiàn)如下:

//AbstractPlatformTransactionManager
/**
* Process an actual rollback.
* The completed flag has already been checked.
* @param status object representing the transaction
* @throws TransactionException in case of rollback failure
*/
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        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);
            }
            else {
                // Participating in larger transaction
                if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        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);

        // 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);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring 事務屬性分析 事務管理對于企業(yè)應用而言至關重要。它保證了用戶的每一次操作都是可靠的,即便出現(xiàn)了異常的...
    壹點零閱讀 1,380評論 0 2
  • 這部分的參考文檔涉及數(shù)據(jù)訪問和數(shù)據(jù)訪問層和業(yè)務或服務層之間的交互。 Spring的綜合事務管理支持覆蓋很多細節(jié),然...
    竹天亮閱讀 1,097評論 0 0
  • 1. Spring 事務簡介 Spring 本身并不實現(xiàn)事務,Spring事務 的本質 還是 底層數(shù)據(jù)庫 對事務的...
    白襪子先生閱讀 13,278評論 0 12
  • 什么是事務 事務就是一組操作數(shù)據(jù)庫的動作集合。 動作集合被完整地執(zhí)行,我們稱該事務被提交。動作集合中的某一部分執(zhí)行...
    超級變換形態(tài)閱讀 767評論 0 6
  • 一、事務的基本原理 Spring事務的本質其實就是數(shù)據(jù)庫對事務的支持,沒有數(shù)據(jù)庫的事務支持,spring是無法提供...
    芭蕾武閱讀 1,732評論 3 12

友情鏈接更多精彩內容