事務(wù)傳播
七種事務(wù)傳播行為詳解與示例
在介紹七種事務(wù)傳播行為前,我們先設(shè)計一個場景,幫助大家理解,場景描述如下
現(xiàn)有兩個方法A和B,方法A執(zhí)行會在數(shù)據(jù)庫ATable插入一條數(shù)據(jù),方法B執(zhí)行會在數(shù)據(jù)庫BTable插入一條數(shù)據(jù),偽代碼如下:
//將傳入?yún)?shù)a存入ATablepubilc void A(a){ insertIntoATable(a); }//將傳入?yún)?shù)b存入BTablepublic void B(b){ insertIntoBTable(b);}
接下來,我們看看在如下場景下,沒有事務(wù),情況會怎樣
public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
在這里要做一個重要提示:Spring中事務(wù)的默認(rèn)實現(xiàn)使用的是AOP,也就是代理的方式,如果大家在使用代碼測試時,同一個Service類中的方法相互調(diào)用需要使用注入的對象來調(diào)用,不要直接使用this.方法名來調(diào)用,this.方法名調(diào)用是對象內(nèi)部方法調(diào)用,不會通過Spring代理,也就是事務(wù)不會起作用
以上偽代碼描述的一個場景,方法testMain和testB都沒有事務(wù),執(zhí)行testMain方法,那么結(jié)果會怎么樣呢?
相信大家都知道了,就是a1數(shù)據(jù)成功存入ATable表,b1數(shù)據(jù)成功存入BTable表,而在拋出異常后b2數(shù)據(jù)存儲就不會執(zhí)行,也就是b2數(shù)據(jù)不會存入數(shù)據(jù)庫,這就是沒有事務(wù)的場景。
接下我們就開始理解七種不同事務(wù)傳播類型的含義
REQUIRED(Spring默認(rèn)的事務(wù)傳播類型)
如果當(dāng)前沒有事務(wù),則自己新建一個事務(wù),如果當(dāng)前存在事務(wù),則加入這個事務(wù)
源碼說明如下:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
(示例1)根據(jù)場景舉栗子,我們在testMain和testB上聲明事務(wù),設(shè)置傳播行為REQUIRED,偽代碼如下:
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.REQUIRED)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
該場景下執(zhí)行testMain方法結(jié)果如何呢?
數(shù)據(jù)庫沒有插入新的數(shù)據(jù),數(shù)據(jù)庫還是保持著執(zhí)行testMain方法之前的狀態(tài),沒有發(fā)生改變。testMain上聲明了事務(wù),在執(zhí)行testB方法時就加入了testMain的事務(wù)(當(dāng)前存在事務(wù),則加入這個事務(wù)),在執(zhí)行testB方法拋出異常后事務(wù)會發(fā)生回滾,又testMain和testB使用的同一個事務(wù),所以事務(wù)回滾后testMain和testB中的操作都會回滾,也就使得數(shù)據(jù)庫仍然保持初始狀態(tài)
(示例2)根據(jù)場景再舉一個栗子,我們只在testB上聲明事務(wù),設(shè)置傳播行為REQUIRED,偽代碼如下:
public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.REQUIRED)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
這時的執(zhí)行結(jié)果又如何呢?
數(shù)據(jù)a1存儲成功,數(shù)據(jù)b1和b2沒有存儲。由于testMain沒有聲明事務(wù),testB有聲明事務(wù)且傳播行為是REQUIRED,所以在執(zhí)行testB時會自己新建一個事務(wù)(如果當(dāng)前沒有事務(wù),則自己新建一個事務(wù)),testB拋出異常則只有testB中的操作發(fā)生了回滾,也就是b1的存儲會發(fā)生回滾,但a1數(shù)據(jù)不會回滾,所以最終a1數(shù)據(jù)存儲成功,b1和b2數(shù)據(jù)沒有存儲
SUPPORTS
當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方法執(zhí)行
源碼注釋如下(太長省略了一部分),其中里面有一個提醒翻譯一下就是:“對于具有事務(wù)同步的事務(wù)管理器,SUPPORTS與完全沒有事務(wù)稍有不同,因為她定義了可能應(yīng)用同步的事務(wù)范圍”。這個是與事務(wù)同步管理器相關(guān)的一個注意項,這里不過多討論。
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
(示例3)根據(jù)場景舉栗子,我們只在testB上聲明事務(wù),設(shè)置傳播行為SUPPORTS,偽代碼如下:
public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.SUPPORTS)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
這種情況下,執(zhí)行testMain的最終結(jié)果就是,a1,b1存入數(shù)據(jù)庫,b2沒有存入數(shù)據(jù)庫。由于testMain沒有聲明事務(wù),且testB的事務(wù)傳播行為是SUPPORTS,所以執(zhí)行testB時就是沒有事務(wù)的(如果當(dāng)前沒有事務(wù),就以非事務(wù)方法執(zhí)行),則在testB拋出異常時也不會發(fā)生回滾,所以最終結(jié)果就是a1和b1存儲成功,b2沒有存儲。
那么當(dāng)我們在testMain上聲明事務(wù)且使用REQUIRED傳播方式的時候,這個時候執(zhí)行testB就滿足當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),在testB拋出異常時事務(wù)就會回滾,最終結(jié)果就是a1,b1和b2都不會存儲到數(shù)據(jù)庫
MANDATORY
當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),如果當(dāng)前事務(wù)不存在,則拋出異常。
源碼注釋如下:
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
(示例4)場景舉栗子,我們只在testB上聲明事務(wù),設(shè)置傳播行為MANDATORY,偽代碼如下:
public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.MANDATORY)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
這種情形的執(zhí)行結(jié)果就是a1存儲成功,而b1和b2沒有存儲。b1和b2沒有存儲,并不是事務(wù)回滾的原因,而是因為testMain方法沒有聲明事務(wù),在去執(zhí)行testB方法時就直接拋出事務(wù)要求的異常(如果當(dāng)前事務(wù)不存在,則拋出異常),所以testB方法里的內(nèi)容就沒有執(zhí)行。
那么如果在testMain方法進行事務(wù)聲明,并且設(shè)置為REQUIRED,則執(zhí)行testB時就會使用testMain已經(jīng)開啟的事務(wù),遇到異常就正常的回滾了。
REQUIRES_NEW
創(chuàng)建一個新事務(wù),如果存在當(dāng)前事務(wù),則掛起該事務(wù)。
可以理解為設(shè)置事務(wù)傳播類型為REQUIRES_NEW的方法,在執(zhí)行時,不論當(dāng)前是否存在事務(wù),總是會新建一個事務(wù)。
源碼注釋如下
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
(示例5)場景舉栗子,為了說明設(shè)置REQUIRES_NEW的方法會開啟新事務(wù),我們把異常發(fā)生的位置換到了testMain,然后給testMain聲明事務(wù),傳播類型設(shè)置為REQUIRED,testB也聲明事務(wù),設(shè)置傳播類型為REQUIRES_NEW,偽代碼如下
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB throw Exception; //發(fā)生異常拋出}@Transactional(propagation = Propagation.REQUIRES_NEW)public void testB(){ B(b1); //調(diào)用B入?yún)1 B(b2); //調(diào)用B入?yún)2}
這種情形的執(zhí)行結(jié)果就是a1沒有存儲,而b1和b2存儲成功,因為testB的事務(wù)傳播設(shè)置為REQUIRES_NEW,所以在執(zhí)行testB時會開啟一個新的事務(wù),testMain中發(fā)生的異常時在testMain所開啟的事務(wù)中,所以這個異常不會影響testB的事務(wù)提交,testMain中的事務(wù)會發(fā)生回滾,所以最終a1就沒有存儲,而b1和b2就存儲成功了。
與這個場景對比的一個場景就是testMain和testB都設(shè)置為REQUIRED,那么上面的代碼執(zhí)行結(jié)果就是所有數(shù)據(jù)都不會存儲,因為testMain和testMain是在同一個事務(wù)下的,所以事務(wù)發(fā)生回滾時,所有的數(shù)據(jù)都會回滾
NOT_SUPPORTED
始終以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)
可以理解為設(shè)置事務(wù)傳播類型為NOT_SUPPORTED的方法,在執(zhí)行時,不論當(dāng)前是否存在事務(wù),都會以非事務(wù)的方式運行。
源碼說明如下
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
(示例6)場景舉栗子,testMain傳播類型設(shè)置為REQUIRED,testB傳播類型設(shè)置為NOT_SUPPORTED,且異常拋出位置在testB中,偽代碼如下
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.NOT_SUPPORTED)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
該場景的執(zhí)行結(jié)果就是a1和b2沒有存儲,而b1存儲成功。testMain有事務(wù),而testB不使用事務(wù),所以執(zhí)行中testB的存儲b1成功,然后拋出異常,此時testMain檢測到異常事務(wù)發(fā)生回滾,但是由于testB不在事務(wù)中,所以只有testMain的存儲a1發(fā)生了回滾,最終只有b1存儲成功,而a1和b1都沒有存儲
NEVER
不使用事務(wù),如果當(dāng)前事務(wù)存在,則拋出異常
很容易理解,就是我這個方法不使用事務(wù),并且調(diào)用我的方法也不允許有事務(wù),如果調(diào)用我的方法有事務(wù)則我直接拋出異常。
源碼注釋如下:
NEVER(TransactionDefinition.PROPAGATION_NEVER),
(示例7)場景舉栗子,testMain設(shè)置傳播類型為REQUIRED,testB傳播類型設(shè)置為NEVER,并且把testB中的拋出異常代碼去掉,則偽代碼如下
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB}@Transactional(propagation = Propagation.NEVER)public void testB(){ B(b1); //調(diào)用B入?yún)1 B(b2); //調(diào)用B入?yún)2}
該場景執(zhí)行,直接拋出事務(wù)異常,且不會有數(shù)據(jù)存儲到數(shù)據(jù)庫。由于testMain事務(wù)傳播類型為REQUIRED,所以testMain是運行在事務(wù)中,而testB事務(wù)傳播類型為NEVER,所以testB不會執(zhí)行而是直接拋出事務(wù)異常,此時testMain檢測到異常就發(fā)生了回滾,所以最終數(shù)據(jù)庫不會有數(shù)據(jù)存入。
NESTED
如果當(dāng)前事務(wù)存在,則在嵌套事務(wù)中執(zhí)行,否則REQUIRED的操作一樣(開啟一個事務(wù))
這里需要注意兩點:
REQUIRES_NEW是新建一個事務(wù)并且新開啟的這個事務(wù)與原有事務(wù)無關(guān),而NESTED則是當(dāng)前存在事務(wù)時(我們把當(dāng)前事務(wù)稱之為父事務(wù))會開啟一個嵌套事務(wù)(稱之為一個子事務(wù))。
在NESTED情況下父事務(wù)回滾時,子事務(wù)也會回滾,而在REQUIRES_NEW情況下,原有事務(wù)回滾,不會影響新開啟的事務(wù)。
REQUIRED情況下,調(diào)用方存在事務(wù)時,則被調(diào)用方和調(diào)用方使用同一事務(wù),那么被調(diào)用方出現(xiàn)異常時,由于共用一個事務(wù),所以無論調(diào)用方是否catch其異常,事務(wù)都會回滾
而在NESTED情況下,被調(diào)用方發(fā)生異常時,調(diào)用方可以catch其異常,這樣只有子事務(wù)回滾,父事務(wù)不受影響
(示例8)場景舉栗子,testMain設(shè)置為REQUIRED,testB設(shè)置為NESTED,且異常發(fā)生在testMain中,偽代碼如下
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 testB(); //調(diào)用testB throw Exception; //發(fā)生異常拋出}@Transactional(propagation = Propagation.NESTED)public void testB(){ B(b1); //調(diào)用B入?yún)1 B(b2); //調(diào)用B入?yún)2}
該場景下,所有數(shù)據(jù)都不會存入數(shù)據(jù)庫,因為在testMain發(fā)生異常時,父事務(wù)回滾則子事務(wù)也跟著回滾了,可以與(示例5)比較看一下,就找出了與REQUIRES_NEW的不同
(示例9)場景舉栗子,testMain設(shè)置為REQUIRED,testB設(shè)置為NESTED,且異常發(fā)生在testB中,偽代碼如下
@Transactional(propagation = Propagation.REQUIRED)public void testMain(){ A(a1); //調(diào)用A入?yún)1 try{ testB(); //調(diào)用testB }catch(Exception e){ } A(a2);}@Transactional(propagation = Propagation.NESTED)public void testB(){ B(b1); //調(diào)用B入?yún)1 throw Exception; //發(fā)生異常拋出 B(b2); //調(diào)用B入?yún)2}
這種場景下,結(jié)果是a1,a2存儲成功,b1和b2存儲失敗,因為調(diào)用方catch了被調(diào)方的異常,所以只有子事務(wù)回滾了。
同樣的代碼,如果我們把testB的傳播類型改為REQUIRED,結(jié)果也就變成了:沒有數(shù)據(jù)存儲成功。就算在調(diào)用方catch了異常,整個事務(wù)還是會回滾,因為,調(diào)用方和被調(diào)方共用的同一個事務(wù)
轉(zhuǎn)自于:https://zhuanlan.zhihu.com/p/148504094