50--Spring @Transactional聲明式事物(七)嵌套事物回滾

1.引

上一節(jié)分析了嵌套事物的創(chuàng)建,本節(jié)分析每種傳播特性的回滾處理過(guò)程。由于這一部分的組合情況會(huì)很多琅摩,我們只分析其中的一兩種情況,更多的大家還是要多看源碼锭硼、多測(cè)試房资! 注意:這里最外層的事物一定要開(kāi)啟,如果將最外層的事物特性設(shè)置為PROPAGATION_NOT_SUPPORTED檀头,則不會(huì)引發(fā)嵌套事物的問(wèn)題志膀。

2.processRollback回顧
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;

        try {

            // 1.事物完成之前的觸發(fā)器調(diào)用
            triggerBeforeCompletion(status);

            // 2.如果有保存點(diǎn),則調(diào)用rollbackToHeldSavepoint回滾到保存點(diǎn)
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
            }
            // 3.如果當(dāng)前事物是一個(gè)新的事物,則調(diào)用doRollback執(zhí)行給定事物的回滾
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                doRollback(status);
            }
            else {
                // Participating in larger transaction
                // 4.如果當(dāng)前事物并非獨(dú)立事物,則將當(dāng)前事物的rollbackOnly屬性標(biāo)記為true,等到事物鏈完成之后,一起執(zhí)行回滾

                // 如果當(dāng)前存在事物,但是
                // 事物的rollbackOnly屬性已經(jīng)被標(biāo)記為true
                // 或者globalRollbackOnParticipationFailure(返回是否僅在參與事務(wù)失敗后才將現(xiàn)有事務(wù)全局標(biāo)記為回滾)為true
                if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        System.out.println("==當(dāng)前事物并非獨(dú)立事物,且RollbackOnly為true\n");
                        // 則將ConnectionHolder中的rollbackOnly標(biāo)記為true
                        doSetRollbackOnly(status);
                    }
                    else {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                        }
                    }
                }
                // 5.如果當(dāng)前不存在事物,則不會(huì)回滾
                // 例如配置了 @Transactional(propagation = Propagation.NOT_SUPPORTED)
                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;
        }

        // 6.事物完成之后的觸發(fā)器調(diào)用
        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 {
        //7.事物完成后清理資源
        cleanupAfterCompletion(status);
    }
}
3.NOT_SUPPORTED

BankService、PersonService鳖擒、AccountService的事物傳播特性依次是:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==調(diào)用BankService的save方法\n");
    System.out.println("==準(zhǔn)備調(diào)用PersonService的save方法\n");
    personService.save();
    System.out.println("==準(zhǔn)備調(diào)用PersonService的save方法\n");
    accountService.save();
    throw new RuntimeException("==AccountService的save方法手動(dòng)拋出異常");
}

@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==調(diào)用PersonService的save方法\n");
    jdbcTemplate.update(insert_sql);
    throw new RuntimeException("==PersonService手動(dòng)拋出異常");
}

@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==調(diào)用AccountService的save方法\n");
    jdbcTemplate.update(insert_sql);
    throw new RuntimeException("==AccountService的save方法手動(dòng)拋出異常");
}

該特性下PersonService溉浙、AccountService并不會(huì)開(kāi)啟事物,在processRollback方法中會(huì)走到第6點(diǎn)蒋荚,所以即便PersonService戳稽、AccountService拋出異常,也不會(huì)回滾期升。但是最外層的BankService是開(kāi)啟事物的惊奇,所以如果BankService里有針對(duì)數(shù)據(jù)庫(kù)的寫(xiě)操作并拋出異常,依然會(huì)回滾播赁。

但是這里是嵌套事物的回滾颂郎,當(dāng)內(nèi)層事物回滾之后不要忘記,外層事物還處在被掛起的狀態(tài)容为,那么外層被掛起的事物如何恢復(fù)呢乓序?就在processRollback方法中的第7點(diǎn)。

4.恢復(fù)掛起事物
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    // 1.將當(dāng)前事物狀態(tài)標(biāo)記為已完成
    status.setCompleted();
    // 2.清除synchronization
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    // 3.事務(wù)完成后清理資源坎背。
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    // 4.從嵌套事物中恢復(fù)被掛起的資源
    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());
    }
}

關(guān)于其他的資源清理涉及的代碼很多替劈,留在后面介紹,這里我們只看如何恢復(fù)被掛起的事物得滤。

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) throws TransactionException {
    if (resourcesHolder != null) {
        Object suspendedResources = resourcesHolder.suspendedResources;
        if (suspendedResources != null) {
            // 恢復(fù)掛起資源
            doResume(transaction, suspendedResources);
        }
        List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
        // 恢復(fù)掛起的事物同步回調(diào)接口
        if (suspendedSynchronizations != null) {
            TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
            TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
            doResumeSynchronization(suspendedSynchronizations);
        }
    }
}

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

public static void bindResource(Object key, Object value) throws IllegalStateException {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    Map<Object, Object> map = resources.get();
    // set ThreadLocal Map if none found
    if (map == null) {
        map = new HashMap<>();
        resources.set(map);
    }
    Object oldValue = map.put(actualKey, value);
    // Transparently suppress a ResourceHolder that was marked as void...
    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
        oldValue = null;
    }
    if (oldValue != null) {
        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +Thread.currentThread().getName() + "]");
    }
}

該方法比較簡(jiǎn)單陨献,獲取被掛起事物后重新綁定到resources對(duì)象即可。resources的定義如下:

private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
5.PROPAGATION_REQUIRES_NEW

該情況下懂更,因?yàn)镻ROPAGATION_REQUIRES_NEW每次都會(huì)新建事物眨业,并掛起已有事物急膀,所以在該傳播特性下,如果PersonService或AccountService發(fā)生異常龄捡,就會(huì)執(zhí)行回滾操作卓嫂。當(dāng)PersonService或AccountService完成回滾之后,恢復(fù)并回滾之前掛起的事物即可墅茉。至于外層被掛起的事物會(huì)不會(huì)回滾命黔,則要根據(jù)外層事物的傳播特性而定。假如外層事物的傳播特性為NOT_SUPPORTED就斤,那么即便外層事物操作了數(shù)據(jù)庫(kù)并拋出異常也不會(huì)被回滾悍募。而且不會(huì)有嵌套事物的問(wèn)題。

6.PROPAGATION_NESTED

該特性下就有可能會(huì)走到processRollback方法的弟2步洋机,即回滾到保存點(diǎn)坠宴。前提是外層開(kāi)啟事物,且內(nèi)層事物必須有異常拋出绷旗。針對(duì)本例即BankService必須開(kāi)啟事物喜鼓,PersonService或AccountService拋出異常。如果PersonService或AccountService沒(méi)有拋出異常衔肢,雖然兩者存在保存點(diǎn)庄岖,但是并不會(huì)回滾,這樣一來(lái)即便BankService拋出異常并回滾也不會(huì)回滾到保存點(diǎn)角骤,因?yàn)楸4纥c(diǎn)是建立在PersonService和AccountService對(duì)應(yīng)的事物上的隅忿。

下面來(lái)看回滾到保存點(diǎn)的實(shí)現(xiàn):

/**
 * 回滾到事物的保存點(diǎn)并釋放保存點(diǎn)資源
 * Roll back to the savepoint that is held for the transaction
 * and release the savepoint right afterwards.
 */
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");
    }
    // 回滾到保存點(diǎn)
    getSavepointManager().rollbackToSavepoint(savepoint);
    // 釋放保存點(diǎn)資源
    getSavepointManager().releaseSavepoint(savepoint);
    setSavepoint(null);
}

public void rollbackToSavepoint(Object savepoint) throws TransactionException {
    // 回滾到保存點(diǎn)并重置RollbackOnly屬性
    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);
    }
}

public void releaseSavepoint(Object savepoint) throws TransactionException {
    ConnectionHolder conHolder = getConnectionHolderForSavepoint();
    try {
        // 釋放保存點(diǎn)資源
        conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);
    }
    catch (Throwable ex) {
        logger.debug("Could not explicitly release JDBC savepoint", ex);
    }
}
7.PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED

對(duì)于這兩種特性,既不會(huì)開(kāi)啟一個(gè)新的事物邦尊,也不會(huì)在原有事物中嵌套運(yùn)行背桐,而是把本身的事物交給已有事物。該情況下會(huì)執(zhí)行到processRollback方法的第4點(diǎn)蝉揍,將rollbackOnly標(biāo)記為true链峭,等到最外層事物事物回滾的時(shí)候一起回滾。即使最外層的事物沒(méi)有拋出異常又沾,內(nèi)層事物的異常也會(huì)被外層事物截獲并將整個(gè)事物進(jìn)行回滾弊仪。

8.總結(jié)

關(guān)于嵌套事物的處理,就先分析到這里捍掺,大家還是要多結(jié)合事物創(chuàng)建過(guò)程撼短、事物傳播特性等多多分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挺勿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子喂柒,更是在濱河造成了極大的恐慌不瓶,老刑警劉巖禾嫉,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蚊丐,居然都是意外死亡熙参,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)麦备,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)孽椰,“玉大人,你說(shuō)我怎么就攤上這事凛篙∈蜇遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵呛梆,是天一觀的道長(zhǎng)锐涯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)填物,這世上最難降的妖魔是什么纹腌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮滞磺,結(jié)果婚禮上升薯,老公的妹妹穿的比我還像新娘。我一直安慰自己击困,他們只是感情好涎劈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著沛励,像睡著了一般责语。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上目派,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天坤候,我揣著相機(jī)與錄音,去河邊找鬼企蹭。 笑死白筹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谅摄。 我是一名探鬼主播徒河,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼送漠!你這毒婦竟也來(lái)了顽照?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎代兵,沒(méi)想到半個(gè)月后尼酿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡植影,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年裳擎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片思币。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鹿响,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谷饿,到底是詐尸還是另有隱情惶我,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布各墨,位于F島的核電站指孤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贬堵。R本人自食惡果不足惜恃轩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黎做。 院中可真熱鬧叉跛,春花似錦、人聲如沸蒸殿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宏所。三九已至酥艳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爬骤,已是汗流浹背充石。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霞玄,地道東北人骤铃。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坷剧,于是被迫代替她去往敵國(guó)和親惰爬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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