Spring的事務(wù)傳播機(jī)制看這一篇就夠了

一杈抢、前言

spring事務(wù)傳播機(jī)制對應(yīng)java web應(yīng)用來說是比較常見的一個方面了凹联,在項目開發(fā)或者面試中經(jīng)常會遇到,希望本篇文章對spring事務(wù)有一定基礎(chǔ)的同學(xué)能夠加深對spring事務(wù)傳播機(jī)制的理解。

spring事務(wù)是在數(shù)據(jù)庫事務(wù)的基礎(chǔ)上進(jìn)行封裝擴(kuò)展,其主要特性如下:

支持原有的數(shù)據(jù)事務(wù)的隔離級別

加入了事務(wù)傳播的概念丐谋,提供多個事務(wù)的合并和隔離的功能

提供聲明式事務(wù),讓業(yè)務(wù)代碼與事務(wù)分離煌珊,事務(wù)更易用

spring提供了三個接口用來使用事務(wù):

TransactionDefinition :事務(wù)定義(規(guī)定了7種類型的事務(wù)傳播行為)

PlatformTransactionManager :事務(wù)的管理(事務(wù)提交号俐,回滾)

TransactionStatus : 事務(wù)運行狀態(tài)(狀態(tài)判斷)

TransactionDefinition.java

package org.springframework.transaction;

public interface TransactionDefinition {

? ? int PROPAGATION_REQUIRED = 0;

? ? int PROPAGATION_SUPPORTS = 1;

? ? int PROPAGATION_MANDATORY = 2;

? ? int PROPAGATION_REQUIRES_NEW = 3;

? ? int PROPAGATION_NOT_SUPPORTED = 4;

? ? int PROPAGATION_NEVER = 5;

? ? int PROPAGATION_NESTED = 6;

? ? int ISOLATION_DEFAULT = -1;

? ? int ISOLATION_READ_UNCOMMITTED = 1;

? ? int ISOLATION_READ_COMMITTED = 2;

? ? int ISOLATION_REPEATABLE_READ = 4;

? ? int ISOLATION_SERIALIZABLE = 8;

? ? int TIMEOUT_DEFAULT = -1;

? ? int getPropagationBehavior();

? ? int getIsolationLevel();

? ? int getTimeout();

? ? boolean isReadOnly();

? ? @org.springframework.lang.Nullable

? ? java.lang.String getName();

}

PlatformTransactionManager.java

package org.springframework.transaction;

public interface PlatformTransactionManager {

? ? org.springframework.transaction.TransactionStatus getTransaction(@org.springframework.lang.Nullable org.springframework.transaction.TransactionDefinition transactionDefinition) throws org.springframework.transaction.TransactionException;

? ? void commit(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;

? ? void rollback(org.springframework.transaction.TransactionStatus transactionStatus) throws org.springframework.transaction.TransactionException;

}

TransactionStatus.java

package org.springframework.transaction;

public interface TransactionStatus extends org.springframework.transaction.SavepointManager, java.io.Flushable {

? ? boolean isNewTransaction();

? ? boolean hasSavepoint();

? ? void setRollbackOnly();

? ? boolean isRollbackOnly();

? ? void flush();

? ? boolean isCompleted();

}

二、基礎(chǔ)概念

什么是事務(wù)傳播行為定庵?

事務(wù)傳播行為就是用來描述由某一個事務(wù)傳播行為修飾的方法被嵌套進(jìn)另一個方法的時刻事務(wù)如何傳播吏饿。

下面用偽代碼說明:

public class Demo1 {

public void methodA(){

? ? //doSomething

? ? methodB();

}

}

public class Demo2 {

@Transaction(Propagation=XXX)

public void methodB(){

? ? //doSomething

}

}

其中methodA()方法嵌套調(diào)用了methodB()方法,methodB()的事務(wù)傳播行為由@Transaction(Propagation=XXX)設(shè)置而決定蔬浙。

三猪落、為什么會有事務(wù)傳播機(jī)制?

spring 對事務(wù)的控制畴博,是使用 aop 切面實現(xiàn)的笨忌,我們不用關(guān)心事務(wù)的開始,提交 俱病,回滾蜜唾,只需要在方法上加 @Transactional 注解,這時候就有問題了庶艾。

場景一:?methodA方法調(diào)用了?methodB 方法,但兩個方法都有事務(wù)擎勘,這個時候如果?methodB 方法異常咱揍,是讓?methodB 方法提交,還是兩個一起回滾棚饵。

場景二:methodA方法調(diào)用了?methodB 方法煤裙,但是只有?methodA 方法加了事務(wù)掩完,是否把?methodB 也加入?methodA 的事務(wù),如果?methodB 異常硼砰,是否回滾?methodA且蓬。場景三:methodA方法調(diào)用了?methodB 方法,兩者都有事務(wù)题翰,methodB 已經(jīng)正常執(zhí)行完恶阴,但?methodA異常,是否需要回滾 serviceB 的數(shù)據(jù)豹障。

四冯事、傳播機(jī)制的生效條件

因為 spring 是使用 aop 來代理事務(wù)控制 ,是針對于接口或類的血公,所以在同一個 service 類中兩個方法的調(diào)用昵仅,傳播機(jī)制是不生效的。

public class DemoClass {

public void methodA(){

? ? //doSomething

? ? methodB();

}

@Transaction(Propagation=XXX)

public void methodB(){

? ? //doSomething

}

}

1累魔、如果在methodA中加入:@Transactional摔笤,mehodB不加@Transactional,那么調(diào)用methodA垦写,methodA的事物會生效吕世,因為methodA默認(rèn)的propagation為PROPAGATION_REQUIRED,此時methodB會加入到methodA中

2梯澜、如果methodA與methodB都加上@Transactional寞冯,那么調(diào)用methodA,methodA的事物會生效晚伙,此時和 1 中情況一樣吮龄,事物會生效,也是由于methodB的事物會加入到methodA中咆疗,但是漓帚,其實methodA中通過this.methodB()的調(diào)用是沒有觸發(fā)spring 的事物,原因會統(tǒng)一講解午磁。

3尝抖、如果methodA配置@Transactional,methodB配置@Transactional( propagation = Propagation.REQUIRES_NEW )迅皇,那么調(diào)用methodA昧辽,如果methodA出現(xiàn)異常,根據(jù)spring的事物傳播行為登颓,其實methodB應(yīng)該是入庫才對搅荞,但是我測試發(fā)現(xiàn)根本沒有生效,一樣是全部回滾。

以上結(jié)果的原因解釋:

spring的事物管理通過AOP代理來實現(xiàn)咕痛, 根據(jù)aop的思想痢甘,不可能在具體類Demo上直接處理事物,而是通過代理類來處理茉贡,代理類在調(diào)用具體類的方法來實現(xiàn)塞栅,根據(jù)上面的情景methodA通過this調(diào)用methodB,那么此時相當(dāng)于調(diào)用methodB時是沒有經(jīng)過代理類的調(diào)用腔丧,因此spring無法對事物的傳播行為做處理放椰。

因此下面的類型都是針對于被調(diào)用方法來說的,理解起來要想象成兩個 不同的service 的方法的調(diào)用才可以悔据。

五庄敛、七種傳播機(jī)制

Propagation.java

package org.springframework.transaction.annotation;

public enum Propagation {

? ? REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED;

? ? private final int value;

? ? private Propagation(int value) { /* compiled code */ }

? ? public int value() { /* compiled code */ }

}

類別事務(wù)傳播類型傳播行為說明

支持當(dāng)前事務(wù)REQUIRED(必須)如果當(dāng)前沒有事務(wù),就新建一個事務(wù)科汗,如果已經(jīng)存在一個事務(wù)中藻烤,加入到這個事務(wù)中合并成一個事務(wù)。這是最常見的選擇(默認(rèn))头滔。

支持當(dāng)前事務(wù)SUPPORTS(支持)支持當(dāng)前事務(wù)有則加入當(dāng)前事務(wù)怖亭,如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行坤检。

支持當(dāng)前事務(wù)MANDATORY(強(qiáng)制)使用當(dāng)前的事務(wù)加入當(dāng)前事務(wù)兴猩,如果當(dāng)前無事務(wù),則拋出異常早歇,也即父級方法必須有事務(wù)倾芝。

不支持當(dāng)前事務(wù)REQUIRES_NEW(隔離)新建事務(wù),如果當(dāng)前存在事務(wù)箭跳,把當(dāng)前事務(wù)掛起晨另。這個方法會獨立提交事務(wù),不受調(diào)用者的事務(wù)影響谱姓,父級異常借尿,它也是正常提交。

不支持當(dāng)前事務(wù)NOT_SUPPORTED(不支持)以非事務(wù)方式執(zhí)行操作屉来,如果當(dāng)前存在事務(wù)路翻,就把當(dāng)前事務(wù)掛起。

不支持當(dāng)前事務(wù)NEVER(強(qiáng)制非事務(wù))以非事務(wù)方式運行茄靠,如果當(dāng)前存在事務(wù)茂契,則拋出異常,即父級方法必須無事務(wù)慨绳。

嵌套事務(wù)NESTED(嵌套事務(wù))如果當(dāng)前存在事務(wù)账嚎,它將會成為父級事務(wù)的一個子事務(wù)莫瞬,方法結(jié)束后并沒有提交,只有等父事務(wù)結(jié)束才提交郭蕉;

如果當(dāng)前沒有事務(wù),則新建事務(wù) 喂江;

如果它異常召锈,父級可以捕獲它的異常而不進(jìn)行回滾,父級正常提交获询,而它回滾事務(wù)不提交?涨岁;

但如果父級異常,它必然回滾吉嚣,這就是和?REQUIRES_NEW?的區(qū)別梢薪。

說明:一般用得比較多的是 PROPAGATION_REQUIRED , REQUIRES_NEW尝哆;REQUIRES_NEW 一般用在子方法需要單獨事務(wù)秉撇。

六、項目中如何使用

下面我們采用實際的項目案例秋泄,驗證以上spring的事務(wù)傳播機(jī)制

創(chuàng)建一個maven 項目spring-propagation

POM.xml

? ? <parent>

? ? ? ? <groupId>org.springframework.boot</groupId>

? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>

? ? ? ? <version>2.0.5.RELEASE</version>

? ? ? ? <relativePath/>

? ? </parent>

? ? <groupId>com.veromca.transaction</groupId>

? ? <artifactId>spring-propagation</artifactId>

? ? <version>1.0-SNAPSHOT</version>

? ? <properties>

? ? ? ? <java.version>1.8</java.version>

? ? </properties>

? ? <dependencies>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.mybatis.spring.boot</groupId>

? ? ? ? ? ? <artifactId>mybatis-spring-boot-starter</artifactId>

? ? ? ? ? ? <version>2.1.0</version>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.projectlombok</groupId>

? ? ? ? ? ? <artifactId>lombok</artifactId>

? ? ? ? ? ? <optional>true</optional>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.springframework.boot</groupId>

? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId>

? ? ? ? ? ? <scope>test</scope>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>org.apache.commons</groupId>

? ? ? ? ? ? <artifactId>commons-lang3</artifactId>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>commons-io</groupId>

? ? ? ? ? ? <artifactId>commons-io</artifactId>

? ? ? ? ? ? <version>2.6</version>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>com.alibaba</groupId>

? ? ? ? ? ? <artifactId>fastjson</artifactId>

? ? ? ? ? ? <version>1.2.30</version>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>mysql</groupId>

? ? ? ? ? ? <artifactId>mysql-connector-java</artifactId>

? ? ? ? </dependency>

? ? ? ? <dependency>

? ? ? ? ? ? <groupId>tk.mybatis</groupId>

? ? ? ? ? ? <artifactId>mapper-spring-boot-starter</artifactId>

? ? ? ? ? ? <version>2.1.5</version>

? ? ? ? </dependency>

? ? </dependencies>

創(chuàng)建兩張表

sys_user為主表琐馆,account為字表

CREATE TABLE `sys_user` (

? `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

? `user_name` varchar(30) NULL COMMENT '用戶名',

? `age` int(2) NULL COMMENT '年齡',

? PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用戶表';

CREATE TABLE `account` (

? `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

? `user_name` varchar(30) NULL COMMENT '用戶名',

? `account_no` varchar(30) NULL COMMENT '賬戶編號',

? PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='賬戶表';

創(chuàng)建對應(yīng)服務(wù)類

PropagationAService.java?

/**

? ? * 測試事務(wù)傳播機(jī)制

? ? * 可修改調(diào)用方法來查看不同的效果

? ? */

? ? @Transactional

? ? public void transactionAll(SysUserPO sysUser, List<AccountPO> accounts){

? ? ? ? sysUserMapper.insert(sysUser);

? ? ? ? //propagationBService.insertAccountsTransactionRequired(accounts);

? ? ? ? //propagationBService.insertAccountsTransactionRequiredNew(accounts);

? ? ? ? //propagationBService.insertAccountsTransactionNotSupport(accounts);

? ? ? ? //propagationBService.insertAccountsTransactionNever(accounts);

? ? ? ? //propagationBService.insertAccountsTransactionMandatory(accounts);

? ? ? ? //propagationBService.insertAccountsTransactionSupport(accounts);

? ? ? ? try {

? ? ? ? ? ? propagationBService.insertAccountsTransactionNested(accounts);

? ? ? ? }catch (Exception e){

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? if(true) throw new IllegalArgumentException("出異常了");

? ? }

PropagationBService.java

@Transactional(propagation = Propagation.REQUIRED)

? ? public void insertAccountsTransactionRequired(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.REQUIRES_NEW)

? ? public void insertAccountsTransactionRequiredNew(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.SUPPORTS)

? ? public void insertAccountsTransactionSupport(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.NOT_SUPPORTED)

? ? public void insertAccountsTransactionNotSupport(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.MANDATORY)

? ? public void insertAccountsTransactionMandatory(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.NEVER)

? ? public void insertAccountsTransactionNever(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");

? ? ? ? }

? ? }

@Transactional(propagation = Propagation.NESTED)

? ? public void insertAccountsTransactionNested(List<AccountPO> accounts) {

? ? ? ? for (AccountPO account : accounts) {

? ? ? ? ? ? accountMapper.insert(account);

? ? ? ? ? ? if (true) throw new IllegalArgumentException("出異常了");

? ? ? ? }

? ? }

測試結(jié)果:

具體測試過程大家可以通過下載本文對應(yīng)源碼親自測試,加深理解恒序。

源碼鏈接:https://gitee.com/veromca/spring-propagation.git

值得注意的是:

測試NEVER 時當(dāng)前存在事務(wù)拋出的異常信息如下(即父級方法必須無事務(wù)):

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

測試MANDATORY?時當(dāng)前不存在事務(wù)拋出的異常信息如下(即父級方法必須有事務(wù)):

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘦麸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歧胁,更是在濱河造成了極大的恐慌滋饲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喊巍,死亡現(xiàn)場離奇詭異屠缭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玄糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門勿她,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阵翎,你說我怎么就攤上這事逢并。” “怎么了郭卫?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵砍聊,是天一觀的道長。 經(jīng)常有香客問我贰军,道長玻蝌,這世上最難降的妖魔是什么蟹肘? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮俯树,結(jié)果婚禮上帘腹,老公的妹妹穿的比我還像新娘。我一直安慰自己许饿,他們只是感情好阳欲,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陋率,像睡著了一般球化。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓦糟,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天筒愚,我揣著相機(jī)與錄音,去河邊找鬼菩浙。 笑死巢掺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芍耘。 我是一名探鬼主播址遇,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斋竞!你這毒婦竟也來了倔约?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坝初,失蹤者是張志新(化名)和其女友劉穎浸剩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳄袍,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡绢要,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拗小。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重罪。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哀九,靈堂內(nèi)的尸體忽然破棺而出剿配,到底是詐尸還是另有隱情,我是刑警寧澤阅束,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布呼胚,位于F島的核電站,受9級特大地震影響息裸,放射性物質(zhì)發(fā)生泄漏蝇更。R本人自食惡果不足惜沪编,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望年扩。 院中可真熱鬧蚁廓,春花似錦、人聲如沸常遂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽克胳。三九已至,卻和暖如春圈匆,著一層夾襖步出監(jiān)牢的瞬間漠另,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工跃赚, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留笆搓,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓纬傲,卻偏偏與公主長得像满败,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叹括,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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

  • 『每日一享』第195天 2018年9月19日 話說人這一輩子活著到底是為了什么算墨?有些人,活得很糊涂汁雷,但有些人活的就...
    Oo夢想締造者oO閱讀 362評論 0 0
  • 趙純心閱讀 265評論 0 0
  • DouFM是我今年8月想到的侠讯,當(dāng)時發(fā)現(xiàn)校內(nèi)竟還沒有一個在線音樂服務(wù)挖藏,于是決定自己做一個。 想法很簡單厢漩,做起來也是這...
    dccrazyboy閱讀 3,118評論 9 11
  • 把筆記本忘到學(xué)校了膜眠,感覺沒了著落,習(xí)慣于用文字記錄一下自己了溜嗜,沒想到當(dāng)初第一時間排斥的我竟將日記堅持了下來宵膨,這對我...
    快樂的老三閱讀 136評論 0 0
  • 前言:子類可以直接繼承父類中的方法,而不需要重新編寫相同的方法粱胜,但有時候子類并不想原封不動的繼承父類中的方法柄驻,而是...
    趙亦晨閱讀 875評論 0 0