48--Spring @Transactional聲明式事物(五)嵌套事物簡介

1.引

前面四節(jié)已經(jīng)簡單介紹了Spring的事物管理嫁赏,當(dāng)然都是基于單個Service單個方法調(diào)用下的绒疗、最簡答的事物管理帝美,還遺留了一些問題,例如事物嵌套處理库快、RollbackOnly屬性說明等等摸袁,接下來的篇幅我們著重介紹Spring中的嵌套事物。在介紹之前先來回顧一下Spring中的事物傳播特性义屏,并通過一個簡單的例子來看感受一下嵌套事物靠汁。

傳播特性名稱 說明
PROPAGATION_REQUIRED 如果當(dāng)前沒有事物,則新建一個事物湿蛔;如果已經(jīng)存在一個事物膀曾,則加入到這個事物中
PROPAGATION_SUPPORTS 支持當(dāng)前事物,如果當(dāng)前沒有事物阳啥,則以非事物方式執(zhí)行
PROPAGATION_MANDATORY 使用當(dāng)前事物添谊,如果當(dāng)前沒有事物,則拋出異常
PROPAGATION_REQUIRES_NEW 新建事物察迟,如果當(dāng)前已經(jīng)存在事物斩狱,則掛起當(dāng)前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式執(zhí)行,如果當(dāng)前存在事物扎瓶,則掛起當(dāng)前事物
PROPAGATION_NEVER 以非事物方式執(zhí)行所踊,如果當(dāng)前存在事物,則拋出異常
PROPAGATION_NESTED 如果當(dāng)前存在事物概荷,則在嵌套事物內(nèi)執(zhí)行秕岛;如果當(dāng)前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同

所謂嵌套事物误证,就是ServiceA調(diào)用了ServiceB的方法继薛,不同的service之間互相進(jìn)行方法調(diào)用,就可能會引發(fā)嵌套事物的問題愈捅。

2.事物嵌套的例子
  • Service
package com.lyc.cn.v2.day09;
public interface AccountService {
    void save() throws RuntimeException;

    void delete() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class AccountServiceImpl implements AccountService {

    private JdbcTemplate jdbcTemplate;

    private static String insert_sql = "insert into account(balance) values (100)";


    @Override
    @Transactional(propagation = Propagation.REQUIRED, 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方法手動拋出異常");
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void delete() throws RuntimeException {

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void beforeCommit(boolean readOnly) {
                System.out.println("==回調(diào),事物提交之前");
                super.beforeCommit(readOnly);
            }

            @Override
            public void afterCommit() {
                System.out.println("==回調(diào),事物提交之后");
                super.afterCommit();
            }

            @Override
            public void beforeCompletion() {
                super.beforeCompletion();
                System.out.println("==回調(diào),事物完成之前");
            }

            @Override
            public void afterCompletion(int status) {
                super.afterCompletion(status);
                System.out.println("==回調(diào),事物完成之后");
            }
        });

        System.out.println("==調(diào)用AccountService的dele方法\n");
        jdbcTemplate.update(insert_sql);

        throw new RuntimeException("==AccountService的delete方法手動拋出異常");
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}


package com.lyc.cn.v2.day09;
public interface BankService  {
    void save() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class BankServiceImpl implements BankService {

    private PersonService personService;
    private AccountService accountService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void save() throws RuntimeException {
        System.out.println("==調(diào)用BankService的save方法\n");
        personService.save();
        accountService.save();
    }

    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }
}

package com.lyc.cn.v2.day09;
public interface PersonService {
    void save() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;


    private static String insert_sql = "insert into account(balance) values (100)";

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


    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

新建三個Service接口及其實現(xiàn)類遏考,AccountService、BankService蓝谨、PersonService灌具。然后在BankService的save方法中調(diào)用了AccountService青团、PersonService的方法。

  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--開啟tx注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--事物管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--數(shù)據(jù)源-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="liyanchao1989@"/>
    </bean>

    <!--jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--業(yè)務(wù)bean-->
    <bean id="accountService" class="com.lyc.cn.v2.day09.AccountServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="personService" class="com.lyc.cn.v2.day09.PersonServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="bankService" class="com.lyc.cn.v2.day09.BankServiceImpl">
        <property name="personService" ref="personService"/>
        <property name="accountService" ref="accountService"/>
    </bean>

    <bean id="myTransactionService" class="com.lyc.cn.v2.day09.MyTransactionService">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

</beans>
  • 測試類及結(jié)果
@Test
public void test2() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day09.xml");
    BankService service = ctx.getBean("bankService", BankService.class);
    service.save();
}
==創(chuàng)建了名為:[com.lyc.cn.v2.day09.BankServiceImpl.save]的事物
==調(diào)用BankService的save方法

==創(chuàng)建了名為:[com.lyc.cn.v2.day09.PersonServiceImpl.save]的事物
==調(diào)用PersonService的save方法

==創(chuàng)建了名為:[com.lyc.cn.v2.day09.AccountServiceImpl.save]的事物
==調(diào)用AccountService的save方法

==準(zhǔn)備回滾com.lyc.cn.v2.day09.AccountServiceImpl.save
==當(dāng)前事物并非獨立事物,且RollbackOnly為true

==準(zhǔn)備回滾com.lyc.cn.v2.day09.BankServiceImpl.save

java.lang.RuntimeException: ==AccountService的save方法手動拋出異常
3.嵌套事物的處理簡介

回顧一下前面講的創(chuàng)建事物的過程咖楣,其中有:

// 2.如果當(dāng)前已經(jīng)存在事物
// 重點:
// 如果當(dāng)前已經(jīng)存在啟動的事物,則根據(jù)本次要新建的事物傳播特性進(jìn)行評估,以決定對新事物的后續(xù)處理
if (isExistingTransaction(transaction)) {
    // Existing transaction found -> check propagation behavior to find out how to behave.
    return handleExistingTransaction(definition, transaction, debugEnabled);
}

這里我們沒有分析督笆,因為從這開始就要涉及到事物的嵌套處理了(在AbstractPlatformTransactionManager類的getTransaction方法中)。

因為我們在在BankService的save方法中調(diào)用了AccountService截歉、PersonService的方法胖腾。那么當(dāng)BankService調(diào)用到另外兩個Servcie方法的時候,因為當(dāng)前已經(jīng)存在事物(BankService)瘪松,那么接下來就要根據(jù)新事物的傳播特性咸作,以決定下一步的處理了。先來感受一下處理過程:

private TransactionStatus handleExistingTransaction(
            TransactionDefinition definition,
            Object transaction,
            boolean debugEnabled) throws TransactionException {

    // 1.PROPAGATION_NEVER --> 以非事物方式執(zhí)行宵睦,如果當(dāng)前存在事物记罚,則拋出異常。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
    }

    // 2.以非事物方式執(zhí)行壳嚎,如果當(dāng)前存在事物桐智,則掛起當(dāng)前事物。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) { logger.debug("Suspending current transaction");}
        // 重點:掛起已有事物
        Object suspendedResources = suspend(transaction);
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 創(chuàng)建新事物,注意:transaction參數(shù)為null,所以這里創(chuàng)建的不是一個真正的事物
        return prepareTransactionStatus(
                definition,
                null,
                false,
                newSynchronization,
                debugEnabled,
                suspendedResources);
    }

    //3.新建事物烟馅,如果當(dāng)前已經(jīng)存在事物说庭,則掛起當(dāng)前事物。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
        }
        // 掛起已有事物
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            // 創(chuàng)建事物
            DefaultTransactionStatus status = newTransactionStatus(
                    definition,
                    transaction,
                    true,
                    newSynchronization,
                    debugEnabled,
                    suspendedResources);
            // 開啟事物
            doBegin(transaction, definition);
            // 初始化事物同步屬性
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }

    // 4.如果當(dāng)前存在事物郑趁,則在嵌套事物內(nèi)執(zhí)行刊驴;如果當(dāng)前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
    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() + "]");
        }

        /**
         * 下面對JtaTransactionManager和AbstractPlatformTransactionManager分別進(jìn)行處理
         */

        // useSavepointForNestedTransaction(),是否為嵌套事務(wù)使用保存點
        // 1.對于JtaTransactionManager-->返回false
        // 2.對于AbstractPlatformTransactionManager-->返回true
        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.
            // 創(chuàng)建保存點在現(xiàn)有spring管理事務(wù),通過TransactionStatus SavepointManager API實現(xiàn)寡润。
            // 通常使用JDBC 3.0保存點捆憎。永遠(yuǎn)不要激活Spring同步。
            DefaultTransactionStatus status = prepareTransactionStatus(definition,transaction,
                    false,
                    false,
                    debugEnabled,
                    null);
            // 創(chuàng)建保存點
            status.createAndHoldSavepoint();
            return status;
        }
        else {
            // 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.
            // 通過嵌套的開始,提交調(diào)用,及回滾調(diào)用進(jìn)行嵌套事務(wù)梭纹。
            // 只對JTA有效,如果已經(jīng)存在JTA事務(wù)躲惰,這里可能會激活Spring同步。
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            DefaultTransactionStatus status = newTransactionStatus(
                    definition,
                    transaction,
                    true,
                    newSynchronization,
                    debugEnabled,
                    null);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
    }

    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    // 處理PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED兩種傳播特性
    // PROPAGATION_REQUIRED --> 如果當(dāng)前沒有事物变抽,則新建一個事物础拨;如果已經(jīng)存在一個事物,則加入到這個事物中绍载。
    // PROPAGATION_SUPPORTS --> 支持當(dāng)前事物太伊,如果當(dāng)前沒有事物,則以非事物方式執(zhí)行逛钻。
    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    // 對于PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED
    // 新事物參與已有事物時,是否驗證已有事物.此屬性值默認(rèn)為false;
    // 如開啟將驗證新事物和已有事物的隔離級別和事物只讀屬性是否相同
    if (isValidateExistingTransaction()) {
        // 驗證事物隔離級別
        // 如果當(dāng)前事物的隔離級別不為默認(rèn)隔離級別,則比較當(dāng)前事物隔離級別與已有事物隔離級別,
        // 如不同,則拋出事物隔離級別不兼容異常
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                Constants isoConstants = DefaultTransactionDefinition.constants;
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
                        (currentIsolationLevel != null ?
                                isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                "(unknown)"));
            }
        }
        // 驗證事物只讀屬性
        // 如果當(dāng)前事物可寫,但是已有的事物是只讀,則拋出異常
        if (!definition.isReadOnly()) {
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is");
            }
        }
    }
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

在該方法中,對于Spring提供的傳播特性分別做了不同的處理锰提,那么接下來我們會對這些傳播特性一一分析曙痘,包括事物的后續(xù)處理芳悲、事物的回滾等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末边坤,一起剝皮案震驚了整個濱河市名扛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茧痒,老刑警劉巖肮韧,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旺订,居然都是意外死亡弄企,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門区拳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拘领,“玉大人,你說我怎么就攤上這事樱调≡妓兀” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵笆凌,是天一觀的道長圣猎。 經(jīng)常有香客問我,道長乞而,這世上最難降的妖魔是什么送悔? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮晦闰,結(jié)果婚禮上放祟,老公的妹妹穿的比我還像新娘。我一直安慰自己呻右,他們只是感情好跪妥,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著声滥,像睡著了一般眉撵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上落塑,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天纽疟,我揣著相機(jī)與錄音,去河邊找鬼憾赁。 笑死污朽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的龙考。 我是一名探鬼主播蟆肆,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼矾睦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炎功?” 一聲冷哼從身側(cè)響起枚冗,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛇损,沒想到半個月后赁温,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡淤齐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年股囊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床玻。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡毁涉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锈死,到底是詐尸還是另有隱情贫堰,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布待牵,位于F島的核電站其屏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缨该。R本人自食惡果不足惜偎行,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贰拿。 院中可真熱鬧蛤袒,春花似錦、人聲如沸膨更。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荚守。三九已至珍德,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矗漾,已是汗流浹背锈候。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留敞贡,地道東北人泵琳。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虑稼。 傳聞我的和親對象是個殘疾皇子琳钉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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