spring事務(wù)Propagation及其實(shí)現(xiàn)原理詳解
本文研究的主要是spring事務(wù)Propagation及其實(shí)現(xiàn)原理,具體介紹如下。
簡(jiǎn)介
spring目前已是java開發(fā)的一個(gè)事實(shí)標(biāo)準(zhǔn),這得益于它的便利、功能齊全、容易上手等特性。在開發(fā)過程當(dāng)中,操作DB是非常常見的操作,而涉及到db,就會(huì)涉及到事務(wù)。事務(wù)在平時(shí)的開發(fā)過程當(dāng)中,就算沒有注意到,程序正常執(zhí)行不會(huì)有副作用,但如果出現(xiàn)了異常,而又沒有處理好事務(wù)的話,可能就會(huì)出現(xiàn)意想不到的結(jié)果。spring在事務(wù)方面進(jìn)行了各種操作的封裝,特別是聲明式事務(wù)的出現(xiàn),讓開發(fā)變得更加的舒心。spring對(duì)事務(wù)進(jìn)行了擴(kuò)展,支持定義多種傳播屬性,這也是本篇要說明的重點(diǎn)。
事務(wù)是什么
非嚴(yán)格的講,一個(gè)事務(wù)是多個(gè)操作的簡(jiǎn)稱,這些操作要么全部生效,要么一個(gè)都不生效(相當(dāng)于沒有執(zhí)行過),一個(gè)通用的操作流程簡(jiǎn)化如下:
try{ Connection conn = getConnection(); // 執(zhí)行一些數(shù)據(jù)庫操作 }catch(Exception e){ conn.rollback(); }finally{ conn.close(); }
從以上代碼可以看出一些問題:
- 太多無用的固定代碼
- 如果一個(gè)請(qǐng)求需要調(diào)用多個(gè)服務(wù)接口,難以更精細(xì)的控制事務(wù)
- 跨多種底層數(shù)據(jù)層,如jdbc,mybatis,hibernate,jta,難以統(tǒng)一編碼方式。
spring提供了聲明式事務(wù),使得我們不用關(guān)注底層的具體實(shí)現(xiàn),屏蔽了多種不同的底層實(shí)現(xiàn)細(xì)節(jié),為了支持多種復(fù)雜業(yè)務(wù)對(duì)事務(wù)的精細(xì)控制,spring提供了事務(wù)的傳播屬性,結(jié)合聲明式事務(wù),成就了一大事務(wù)利器。
spring事務(wù)傳播屬性示例分析
在TransactionDefinition類中,spring提供了6種傳播屬性,接下來分別用簡(jiǎn)單示例來說明。
溫馨提醒:下文提到的加入當(dāng)前事務(wù),指的是底層使用同一個(gè)Connection,但是事務(wù)狀態(tài)對(duì)象是可以重新創(chuàng)建的,并不影響。文章提到的當(dāng)前只存在一個(gè)事務(wù),表示的是共用底層的一個(gè)Connection,而不在乎創(chuàng)建了多少個(gè)事務(wù)狀態(tài)對(duì)象(TransactionStatus)。
1、PROPAGATION_REQUIRED
說明: 如果當(dāng)前已經(jīng)存在事務(wù),那么加入該事務(wù),如果不存在事務(wù),創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值。
看一個(gè)小例子,代碼如下:
@Transactional public void service(){ serviceA(); serviceB(); } @Transactional serviceA(); @Transactional serviceB();
serviceA 和 serviceB 都聲明了事務(wù),默認(rèn)情況下,propagation=PROPAGATION_REQUIRED,整個(gè)service調(diào)用過程中,只存在一個(gè)共享的事務(wù),當(dāng)有任何異常發(fā)生的時(shí)候,所有操作回滾。
2、PROPAGATION_SUPPORTS
說明:如果當(dāng)前已經(jīng)存在事務(wù),那么加入該事務(wù),否則創(chuàng)建一個(gè)所謂的空事務(wù)(可以認(rèn)為無事務(wù)執(zhí)行)。
看一個(gè)小例子,代碼如下:
public void service(){ serviceA(); throw new RunTimeException(); } @Transactional(propagation=Propagation.SUPPORTS) serviceA();
serviceA執(zhí)行時(shí)當(dāng)前沒有事務(wù),所以service中拋出的異常不會(huì)導(dǎo)致 serviceA回滾。
再看一個(gè)小例子,代碼如下:
public void service(){ serviceA(); } @Transactional(propagation=Propagation.SUPPORTS) serviceA(){ do sql 1 1/0; do sql 2 }
由于serviceA運(yùn)行時(shí)沒有事務(wù),這時(shí)候,如果底層數(shù)據(jù)源defaultAutoCommit=true,那么sql1是生效的,如果defaultAutoCommit=false,那么sql1無效,如果service有@Transactional標(biāo)簽,serviceA共用service的事務(wù)(不再依賴defaultAutoCommit),此時(shí),serviceA全部被回滾。
3、 PROPAGATION_MANDATORY
說明:當(dāng)前必須存在一個(gè)事務(wù),否則拋出異常。
看一個(gè)小例子,代碼如下:
public void service(){ serviceB(); serviceA(); } serviceB(){ do sql } @Transactional(propagation=Propagation.MANDATORY) serviceA(){ do sql }
這種情況執(zhí)行 service會(huì)拋出異常,如果defaultAutoCommit=true,則serviceB是不會(huì)回滾的,defaultAutoCommit=false,則serviceB執(zhí)行無效。
4、PROPAGATN_REQUIRES_NEW
說明:如果當(dāng)前存在事務(wù),先把當(dāng)前事務(wù)相關(guān)內(nèi)容封裝到一個(gè)實(shí)體,然后重新創(chuàng)建一個(gè)新事務(wù),接受這個(gè)實(shí)體為參數(shù),用于事務(wù)的恢復(fù)。更直白的說法就是暫停當(dāng)前事務(wù)(當(dāng)前無事務(wù)則不需要),創(chuàng)建一個(gè)新事務(wù)。 針對(duì)這種情況,兩個(gè)事務(wù)沒有依賴關(guān)系,可以實(shí)現(xiàn)新事務(wù)回滾了,但外部事務(wù)繼續(xù)執(zhí)行。
看一個(gè)小例子,代碼如下:
@Transactional public void service(){ serviceB(); try{ serviceA(); }catch(Exception e){ } } serviceB(){ do sql } @Transactional(propagation=Propagation.REQUIRES_NEW) serviceA(){ do sql 1 1/0; do sql 2 }
當(dāng)調(diào)用service接口時(shí),由于serviceA使用的是REQUIRES_NEW,它會(huì)創(chuàng)建一個(gè)新的事務(wù),但由于serviceA拋出了運(yùn)行時(shí)異常,導(dǎo)致serviceA整個(gè)被回滾了,而在service方法中,捕獲了異常,所以serviceB是正常提交的。 注意,service中的try … catch 代碼是必須的,否則service也會(huì)拋出異常,導(dǎo)致serviceB也被回滾。
5、Propagation.NOT_SUPPORTED
說明:如果當(dāng)前存在事務(wù),掛起當(dāng)前事務(wù),然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行,沒有spring事務(wù)的環(huán)境下,sql的提交完全依賴于 defaultAutoCommit屬性值 。
看一個(gè)小例子,代碼如下:
@Transactional public void service(){ serviceB(); serviceA(); } serviceB(){ do sql } @Transactional(propagation=Propagation.NOT_SUPPORTED) serviceA(){ do sql 1 1/0; do sql 2 }
當(dāng)調(diào)用service方法的時(shí)候,執(zhí)行到serviceA方法中的1/0代碼時(shí),拋出了異常,由于serviceA處于無事務(wù)環(huán)境下,所以 sql1是否生效取決于defaultAutoCommit的值,當(dāng)defaultAutoCommit=true時(shí),sql1是生效的,但是service由于拋出了異常,所以serviceB會(huì)被回滾。
6、 PROPAGATION_NEVER
說明: 如果當(dāng)前存在事務(wù),則拋出異常,否則在無事務(wù)環(huán)境上執(zhí)行代碼。
看一個(gè)小例子,代碼如下:
public void service(){ serviceB(); serviceA(); } serviceB(){ do sql } @Transactional(propagation=Propagation.NEVER) serviceA(){ do sql 1 1/0; do sql 2 }
上面的示例調(diào)用service后,若defaultAutoCommit=true,則serviceB方法及serviceA中的sql1都會(huì)生效。
7、 PROPAGATION_NESTED
說明: 如果當(dāng)前存在事務(wù),則使用 SavePoint 技術(shù)把當(dāng)前事務(wù)狀態(tài)進(jìn)行保存,然后底層共用一個(gè)連接,當(dāng)NESTED內(nèi)部出錯(cuò)的時(shí)候,自行回滾到 SavePoint這個(gè)狀態(tài),只要外部捕獲到了異常,就可以繼續(xù)進(jìn)行外部的事務(wù)提交,而不會(huì)受到內(nèi)嵌業(yè)務(wù)的干擾,但是,如果外部事務(wù)拋出了異常,整個(gè)大事務(wù)都會(huì)回滾。
注意: spring配置事務(wù)管理器要主動(dòng)指定 nestedTransactionAllowed=true,如下所示:
<bean id="dataTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataDataSource" /> <property name="nestedTransactionAllowed" value="true" /> </bean>
看一個(gè)小例子,代碼如下:
@Transactional public void service(){ serviceA(); try{ serviceB(); }catch(Exception e){ } } serviceA(){ do sql } @Transactional(propagation=Propagation.NESTED) serviceB(){ do sql1 1/0; do sql2 }
serviceB是一個(gè)內(nèi)嵌的業(yè)務(wù),內(nèi)部拋出了運(yùn)行時(shí)異常,所以serviceB整個(gè)被回滾了,由于service捕獲了異常,所以serviceA是可以正常提交的。
再來看一個(gè)例子,代碼如下:
@Transactional public void service(){ serviceA(); serviceB(); 1/0; } @Transactional(propagation=Propagation.NESTED) serviceA(){ do sql } serviceB(){ do sql }
由于service拋出了異常,所以會(huì)導(dǎo)致整個(gè)service方法被回滾。(這就是跟PROPAGATION_REQUIRES_NEW不一樣的地方了,NESTED方式下的內(nèi)嵌業(yè)務(wù)會(huì)受到外部事務(wù)的異常而回滾。)
實(shí)現(xiàn)淺析
前面舉例說明了spring事務(wù)提供的幾種傳播屬性,用于滿足多種不同的業(yè)務(wù)需求,大家可以依業(yè)務(wù)而定。接著我們?cè)賮砜纯磗pring實(shí)現(xiàn)這些傳播屬性最重要的技術(shù)依賴是什么。本小節(jié)列舉 PROPAGATION_REQUIRES_NEW 和 Propagation.NESTED 分別進(jìn)行簡(jiǎn)要說明。
1、 PROPAGATION_REQUIRES_NEW 實(shí)現(xiàn)原理
如下的代碼調(diào)用:
@Transactional public void service(){ serviceB(); try{ serviceA(); }catch(Exception e){ } } @Transactional(propagation=Propagation.REQUIRES_NEW) serviceA(){ do sql 1 1/0; do sql 2 } serviceB(){ do sql }
執(zhí)行原理圖如下:
a. 創(chuàng)建事務(wù)狀態(tài)對(duì)象,獲取一個(gè)新的連接,重置連接的 autoCommit,fetchSize,timeout等屬性
b. 把連接綁定到ThreadLocal變量
c. 掛起當(dāng)前事務(wù),把當(dāng)前事務(wù)狀態(tài)對(duì)象,連接等信息封裝成一SuspendedResources對(duì)象,可用于恢復(fù)
d. 創(chuàng)建新的事務(wù)狀態(tài)對(duì)象,重新獲取新的連接,重置新連接的 autoCommit,fetchSize,timeout等屬性,同時(shí),保存SuspendedResources對(duì)象,用于事務(wù)的恢復(fù),把新的連接綁定到ThreadLocal變量(覆蓋操作)
e. 捕獲到異常,回滾ThreadLocal中的連接,恢復(fù)連接參數(shù),關(guān)閉連接,恢復(fù)SuspendedResources
f. 提交ThreadLocal變量中的連接(導(dǎo)致serviceB被提交),還原連接參數(shù),關(guān)閉連接,連接歸還數(shù)據(jù)源
所以程序執(zhí)行的結(jié)果就是 serviceA被回滾了,serviceB成功提交了。
2、 PROPAGATION_NESTED 實(shí)現(xiàn)原理
如下的代碼調(diào)用:
@Transactional public void service(){ serviceA(); try{ serviceB(); }catch(Exception e){ } } serviceA(){ do sql } @Transactional(propagation=Propagation.NESTED) serviceB(){ do sql1 1/0; do sql2 }
執(zhí)行原理圖如下:
a. 創(chuàng)建事務(wù)狀態(tài)對(duì)象,獲取一個(gè)新的連接,重置連接的 autoCommit,fetchSize,timeout等屬性
b. 把連接綁定到ThreadLocal變量
c. 標(biāo)記使用當(dāng)前事務(wù)狀態(tài)對(duì)象,獲取ThreadLocal連接對(duì)象,保存當(dāng)前連接的SavePoint,用于異常恢復(fù),此時(shí)的SavePoint就是執(zhí)行完serviceA后的狀態(tài)
d. 捕獲到異常,使用c中的SavePoint進(jìn)行事務(wù)回滾,也就是把狀態(tài)回滾到執(zhí)行serviceA后的狀態(tài),serviceB方法所有執(zhí)行不生效
e. 獲取ThreadLocal中的連接對(duì)象,提交事務(wù),恢復(fù)連接屬性,關(guān)閉連接
其它
spring在底層數(shù)據(jù)源的基礎(chǔ)上,利用 ThreadLocal,SavePoint等技術(shù)點(diǎn)實(shí)現(xiàn)了多種事務(wù)傳播屬性,便于實(shí)現(xiàn)各種復(fù)雜的業(yè)務(wù)。只有理解了傳播屬性的原理才能更好的駕馭spring事務(wù)。Spring回滾事務(wù)依賴于對(duì)異常的捕獲,默認(rèn)情況下,只有拋出RuntimeException和Error才會(huì)回滾事務(wù),當(dāng)然可以進(jìn)行配置,更多信息可以查看 @Transactional 這個(gè)注解。
總結(jié)
spring的聲明式事務(wù)給我們帶來了極大的便利,為了用好這把利器,理解底層的原理還是有必要的,本篇只是spring事務(wù)的冰山一角,讀者可以在此基礎(chǔ)上自行深入探索。
以上就是本文關(guān)于spring事務(wù)Propagation及其實(shí)現(xiàn)原理詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
如何在Maven項(xiàng)目配置pom.xml指定JDK版本和編碼
maven是個(gè)項(xiàng)目管理工具,如果我們不告訴它要使用什么樣的jdk版本編譯,它就會(huì)用maven-compiler-plugin默認(rèn)的jdk版本來處理,這樣就容易出現(xiàn)版本不匹配的問題,這篇文章主要給大家介紹了關(guān)于如何在Maven項(xiàng)目配置pom.xml指定JDK版本和編碼的相關(guān)資料,需要的朋友可以參考下2024-01-01Springboot項(xiàng)目啟動(dòng)找不到啟動(dòng)類的解決
這篇文章主要介紹了Springboot項(xiàng)目啟動(dòng)找不到啟動(dòng)類的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08java實(shí)現(xiàn)把兩個(gè)有序數(shù)組合并到一個(gè)數(shù)組的實(shí)例
今天小編就為大家分享一篇java實(shí)現(xiàn)把兩個(gè)有序數(shù)組合并到一個(gè)數(shù)組的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-05-05Springboot+Thymeleaf+Jpa實(shí)現(xiàn)登錄功能(附源碼)
最近有學(xué)習(xí)到關(guān)于Springboot+Thymeleaf+Jpa的綜合運(yùn)用知識(shí),因此想寫一個(gè)簡(jiǎn)單的登錄界面來嘗試一下,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之校園一卡通系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+Springboot+Maven+mybatis+Vue+mysql+wd開發(fā)的校園一卡通系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有校園一卡通系統(tǒng)該有的所有功能,感興趣的朋友快來看看吧2022-01-01淺析Java Mail無法解析帶分號(hào)的收件人列表的問題
JAVA MAIL嚴(yán)格按照RFC 822規(guī)范進(jìn)行操作,沒有對(duì)分號(hào)做處理。大多數(shù)郵件服務(wù)器都是嚴(yán)格遵循RFC 822規(guī)范的2013-08-08Springboot之自定義全局異常處理的實(shí)現(xiàn)
這篇文章主要介紹了Springboot之自定義全局異常處理的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Java簡(jiǎn)單實(shí)現(xiàn)定時(shí)器
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)單實(shí)現(xiàn)定時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04