Spring 采用保存點(diǎn)(Savepoint)實(shí)現(xiàn)嵌套事務(wù)原理

1 概述

在Spring事務(wù)中栈幸,我們可以配置事務(wù)的傳播屬性瞪讼,傳播屬性的處理在函數(shù)AbstractPlatformTransactionManager.handleExistingTransaction中楼入,具體可參考源碼磷仰。關(guān)于Spring中傳播屬性的定義可見(jiàn)其官方文檔Transaction Propagation姆蘸,對(duì)于本文介紹的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.

可見(jiàn)祝蝠,Spring采用一個(gè)物理事務(wù)音诈,但是結(jié)合著savepoint機(jī)制(MySql中稱為保存點(diǎn))實(shí)現(xiàn)一個(gè)事務(wù)中的指定范圍提交幻碱。

2 保存點(diǎn)創(chuàng)建準(zhǔn)備

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

這里我們看AbstractPlatformTransactionManager子類(lèi)DataSourceTransactionManager.doGetTransaction實(shí)現(xiàn)

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

//DataSourceTransactionManager
@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    //看下方列出的DataSourceTransactionManager構(gòu)造函數(shù)细溅,
    //isNestedTransactionAllowed會(huì)返回true
    //就是默認(rèn)支持嵌套事務(wù)
    //而嵌套事務(wù)又是采用savepoint實(shí)現(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);
}

上面已經(jīng)介紹了褥傍,如果支持嵌套事務(wù),則創(chuàng)建的DataSourceTransactionObject.isSavepointAllowed會(huì)被設(shè)為true喇聊。

3 保存點(diǎn)創(chuàng)建

在第一章概述中說(shuō)到恍风,如果傳播屬性設(shè)為PROPAGATION_NESTED,如果創(chuàng)建事務(wù)時(shí)已經(jīng)存在了一個(gè)事務(wù)誓篱,則會(huì)創(chuàng)建一個(gè)嵌套事務(wù):

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

    ...
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        //如果當(dāng)前TransactionManager不支持嵌套事務(wù)
        //直接拋錯(cuò)
        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() + "]");
        }
        //判斷當(dāng)前TransactionManager實(shí)現(xiàn)是否是采用保存點(diǎn)實(shí)現(xiàn)嵌套事務(wù)
        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)建保存點(diǎn)
            status.createAndHoldSavepoint();
            return status;
        }
        else {
            //使用嵌套的begin朋贬、commit/rollback實(shí)現(xiàn)嵌套事務(wù),MySql
            //不支持窜骄,因?yàn)槿绻呀?jīng)調(diào)用過(guò)begin锦募,提交之前再調(diào)用
            //begin操作,MySql會(huì)隱式調(diào)用一次commit邻遏,不能達(dá)到嵌套事務(wù)
            //的效果糠亩,這種方式某些數(shù)據(jù)庫(kù)可能會(huì)支持,這里不做介紹
            // 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;
        }
    }

    ...
}

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

DefaultTransactionStatus.createAndHoldSavepoint在其父類(lèi)AbstractTransactionStatus實(shí)現(xiàn):

//AbstractTransactionStatus
//具體怎么創(chuàng)建的不再展開(kāi)准验,可以自行查看代碼
/**
* 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)建保存點(diǎn)之后同時(shí)會(huì)保存該保存點(diǎn)赎线,也就是上面setSavepoint的調(diào)用。

4 保存點(diǎn)提交或釋放

4.1 保存點(diǎn)提交

在事務(wù)完成之后沟娱,會(huì)進(jìn)行事務(wù)提交氛驮,具體的會(huì)調(diào)用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;
            //如果當(dāng)前status有保存點(diǎn),表示當(dāng)前提交的是嵌套在某個(gè)事務(wù)
            //內(nèi)的子事務(wù)济似,通過(guò)釋放保存點(diǎn)提交
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Releasing transaction savepoint");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                //釋放保存的保存點(diǎn)
                status.releaseHeldSavepoint();
            }//else表示通過(guò)begin矫废、commit/rollback實(shí)現(xiàn)子事務(wù),這里不介紹
            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 保存點(diǎn)釋放

發(fā)生異常時(shí)的回滾操作實(shí)現(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);
            //如果有保存點(diǎn)砰蠢,同樣是對(duì)當(dāng)前保存點(diǎn)進(jìn)行回滾蓖扑,
            //依此達(dá)到部分回滾的功能
            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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市台舱,隨后出現(xiàn)的幾起案子律杠,更是在濱河造成了極大的恐慌,老刑警劉巖竞惋,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柜去,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拆宛,警方通過(guò)查閱死者的電腦和手機(jī)嗓奢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浑厚,“玉大人股耽,你說(shuō)我怎么就攤上這事根盒。” “怎么了物蝙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵炎滞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我诬乞,道長(zhǎng)册赛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任震嫉,我火速辦了婚禮击奶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘责掏。我一直安慰自己柜砾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布换衬。 她就那樣靜靜地躺著痰驱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞳浦。 梳的紋絲不亂的頭發(fā)上担映,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音叫潦,去河邊找鬼蝇完。 笑死,一個(gè)胖子當(dāng)著我的面吹牛矗蕊,可吹牛的內(nèi)容都是我干的短蜕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傻咖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朋魔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起卿操,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤警检,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后害淤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扇雕,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年窥摄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镶奉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腮鞍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莹菱,我是刑警寧澤移国,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站道伟,受9級(jí)特大地震影響迹缀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜜徽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一祝懂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拘鞋,春花似錦砚蓬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至隔躲,卻和暖如春摩梧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宣旱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工仅父, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浑吟。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓笙纤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親组力。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粪糙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring 事務(wù)屬性分析 事務(wù)管理對(duì)于企業(yè)應(yīng)用而言至關(guān)重要。它保證了用戶的每一次操作都是可靠的忿项,即便出現(xiàn)了異常的...
    壹點(diǎn)零閱讀 1,305評(píng)論 0 2
  • 這部分的參考文檔涉及數(shù)據(jù)訪問(wèn)和數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)或服務(wù)層之間的交互蓉冈。 Spring的綜合事務(wù)管理支持覆蓋很多細(xì)節(jié),然...
    竹天亮閱讀 1,038評(píng)論 0 0
  • 1. Spring 事務(wù)簡(jiǎn)介 Spring 本身并不實(shí)現(xiàn)事務(wù)轩触,Spring事務(wù) 的本質(zhì) 還是 底層數(shù)據(jù)庫(kù) 對(duì)事務(wù)的...
    白襪子先生閱讀 13,059評(píng)論 0 12
  • 什么是事務(wù) 事務(wù)就是一組操作數(shù)據(jù)庫(kù)的動(dòng)作集合寞酿。 動(dòng)作集合被完整地執(zhí)行,我們稱該事務(wù)被提交脱柱。動(dòng)作集合中的某一部分執(zhí)行...
    超級(jí)變換形態(tài)閱讀 683評(píng)論 0 6
  • 一榨为、事務(wù)的基本原理 Spring事務(wù)的本質(zhì)其實(shí)就是數(shù)據(jù)庫(kù)對(duì)事務(wù)的支持惨好,沒(méi)有數(shù)據(jù)庫(kù)的事務(wù)支持煌茴,spring是無(wú)法提供...
    芭蕾武閱讀 1,695評(píng)論 3 12