日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

三问Spring事务:解决什么问题?如何解决?存在什么问题?

發(fā)布時(shí)間:2025/3/16 javascript 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 三问Spring事务:解决什么问题?如何解决?存在什么问题? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 解決什么問(wèn)題

讓我們先從事務(wù)說(shuō)起,“什么是事務(wù)?我們?yōu)槭裁葱枰聞?wù)?”。事務(wù)是一組無(wú)法被分割的操作,要么所有操作全部成功,要么全部失敗。我們?cè)陂_(kāi)發(fā)中需要通過(guò)事務(wù)將一些操作組成一個(gè)單元,來(lái)保證程序邏輯上的正確性,例如全部插入成功,或者回滾,一條都不插入。作為程序員的我們,對(duì)于事務(wù)管理,所需要做的便是進(jìn)行事務(wù)的界定,即通過(guò)類(lèi)似begin transaction和end transaction的操作來(lái)界定事務(wù)的開(kāi)始和結(jié)束。

下面是一個(gè)基本的JDBC事務(wù)管理代碼:

//?開(kāi)啟數(shù)據(jù)庫(kù)連接 Connection?con?=?openConnection(); try?{//?關(guān)閉自動(dòng)提交con.setAutoCommit(false);//?業(yè)務(wù)處理//?...??//?提交事務(wù)con.commit(); }?catch?(SQLException?|?MyException?e)?{//?捕獲異常,回滾事務(wù)try?{con.rollback();}?catch?(SQLException?ex)?{ex.printStackTrace();} }?finally?{//?關(guān)閉連接try?{con.setAutoCommit(true);con.close();}?catch?(SQLException?e)?{e.printStackTrace();} }

直接使用JDBC進(jìn)行事務(wù)管理的代碼直觀上來(lái)看,存在兩個(gè)問(wèn)題:

  • 業(yè)務(wù)處理代碼與事務(wù)管理代碼混雜;

  • 大量的異常處理代碼(在catch中還要try-catch)。

  • 而如果我們需要更換其他數(shù)據(jù)訪(fǎng)問(wèn)技術(shù),例如Hibernate、MyBatis、JPA等,雖然事務(wù)管理的操作都類(lèi)似,但API卻不同,則需使用相應(yīng)的API來(lái)改寫(xiě)。這也會(huì)引來(lái)第三個(gè)問(wèn)題:

  • 繁雜的事務(wù)管理API。

  • 上文列出了三個(gè)待解決的問(wèn)題,下面我們看Spring事務(wù)是如何解決。

    ??

    2. 如何解決

    2.1 繁雜的事務(wù)管理API

    針對(duì)該問(wèn)題,我們很容易可以想到,在眾多事務(wù)管理的API上抽象一層。通過(guò)定義接口屏蔽具體實(shí)現(xiàn),再使用策略模式來(lái)決定具體的API。下面我們看下Spring事務(wù)中定義的抽象接口。

    在Spring事務(wù)中,核心接口是PlatformTransactionManager,也叫事務(wù)管理器,其定義如下:

    public?interface?PlatformTransactionManager?extends?TransactionManager?{//?獲取事務(wù)(新的事務(wù)或者已經(jīng)存在的事務(wù))TransactionStatus?getTransaction(@Nullable?TransactionDefinition?definition)throws?TransactionException;???//?提交事務(wù)void?commit(TransactionStatus?status)?throws?TransactionException;//?回滾事務(wù)void?rollback(TransactionStatus?status)?throws?TransactionException; }

    getTransaction通過(guò)入?yún)ransactionDefinition來(lái)獲得TransactionStatus,即通過(guò)定義的事務(wù)元信息來(lái)創(chuàng)建相應(yīng)的事務(wù)對(duì)象。在TransactionDefinition中會(huì)包含事務(wù)的元信息

    • PropagationBehavior:傳播行為;

    • IsolationLevel:隔離級(jí)別;

    • Timeout:超時(shí)時(shí)間;

    • ReadOnly:是否只讀。

    根據(jù)TransactionDefinition獲得的TransactionStatus中會(huì)封裝事務(wù)對(duì)象,并提供了操作事務(wù)查看事務(wù)狀態(tài)的方法,例如:

    • setRollbackOnly:標(biāo)記事務(wù)為Rollback-only,以使其回滾;

    • isRollbackOnly:查看是否被標(biāo)記為Rollback-only;

    • isCompleted:查看事務(wù)是否已完成(提交或回滾完成)。

    還支持嵌套事務(wù)的相關(guān)方法:

    • createSavepoint:創(chuàng)建savepoint;

    • rollbackToSavepoint:回滾到指定savepoint;

    • releaseSavePoint:釋放savepoint。

    TransactionStatus事務(wù)對(duì)象可被傳入到commit方法或rollback方法中,完成事務(wù)的提交或回滾。

    下面我們通過(guò)一個(gè)具體實(shí)現(xiàn)來(lái)理解TransactionStatus的作用。以commit方法為例,如何通過(guò)TransactionStatus完成事務(wù)的提交。AbstractPlatformTransactionManager是PlatformTransactionManager接口的的實(shí)現(xiàn),作為模板類(lèi),其commit實(shí)現(xiàn)如下:

    public?final?void?commit(TransactionStatus?status)?throws?TransactionException?{//?1.檢查事務(wù)是否已完成if?(status.isCompleted())?{throw?new?IllegalTransactionStateException("Transaction?is?already?completed?-?do?not?call?commit?or?rollback?more?than?once?per?transaction");}//?2.檢查事務(wù)是否需要回滾(局部事務(wù)回滾)DefaultTransactionStatus?defStatus?=?(DefaultTransactionStatus)?status;if?(defStatus.isLocalRollbackOnly())?{if?(defStatus.isDebug())?{logger.debug("Transactional?code?has?requested?rollback");}processRollback(defStatus,?false);return;}//?3.檢查事務(wù)是否需要回滾(全局事務(wù)回滾)if?(!shouldCommitOnGlobalRollbackOnly()?&&?defStatus.isGlobalRollbackOnly())?{if?(defStatus.isDebug())?{logger.debug("Global?transaction?is?marked?as?rollback-only?but?transactional?code?requested?commit");}processRollback(defStatus,?true);return;}//?4.提交事務(wù)processCommit(defStatus); }

    在commit模板方法中定義了事務(wù)提交的基本邏輯,通過(guò)查看status的事務(wù)狀態(tài)來(lái)決定拋出異常還是回滾,或是提交。其中的processRollback和processCommit方法也是模板方法,進(jìn)一步定義了回滾、提交的邏輯。以processCommit方法為例,具體的提交操作將由抽象方法doCommit完成。

    protected?abstract?void?doCommit(DefaultTransactionStatus?status)?throws?TransactionException;

    doCommit的實(shí)現(xiàn)取決于具體的數(shù)據(jù)訪(fǎng)問(wèn)技術(shù)。我們看下JDBC相應(yīng)的具體實(shí)現(xiàn)類(lèi)DataSourceTransactionManager中的doCommit實(shí)現(xiàn)。

    protected?void?doCommit(DefaultTransactionStatus?status)?{//?獲取status中的事務(wù)對(duì)象????DataSourceTransactionObject?txObject?=?(DataSourceTransactionObject)?status.getTransaction();//?通過(guò)事務(wù)對(duì)象獲得數(shù)據(jù)庫(kù)連接對(duì)象Connection?con?=?txObject.getConnectionHolder().getConnection();if?(status.isDebug())?{logger.debug("Committing?JDBC?transaction?on?Connection?["?+?con?+?"]");}try?{//?執(zhí)行commitcon.commit();}catch?(SQLException?ex)?{throw?new?TransactionSystemException("Could?not?commit?JDBC?transaction",?ex);} }

    在commit和processCommit方法中我們根據(jù)入?yún)⒌腡ransactionStatus提供的事務(wù)狀態(tài)來(lái)決定事務(wù)行為,而在doCommit中需要執(zhí)行事務(wù)提交時(shí)將會(huì)通過(guò)TransactionStatus中的事務(wù)對(duì)象來(lái)獲得數(shù)據(jù)庫(kù)連接對(duì)象,再執(zhí)行最后的commit操作。通過(guò)這個(gè)示例我們可以理解TransactionStatus所提供的事務(wù)狀態(tài)和事務(wù)對(duì)象的作用。

    下面是用Spring事務(wù)API改寫(xiě)后的事務(wù)管理代碼:

    //?獲得事務(wù)管理器 PlatformTransactionManager?txManager?=?getPlatformTransactionManager(); DefaultTransactionDefinition?def?=?new?DefaultTransactionDefinition(); //?指定事務(wù)元信息 def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //?獲得事務(wù) TransactionStatus?status?=?txManager.getTransaction(def); try?{//?業(yè)務(wù)處理 } catch?(MyException?ex)?{//?捕獲異常,回滾事務(wù)txManager.rollback(status);throw?ex; } //?提交事務(wù) txManager.commit(status);

    無(wú)論是使用JDBC、Hibernate還是MyBatis,我們只需要傳給txManager相應(yīng)的具體實(shí)現(xiàn)就可以在多種數(shù)據(jù)訪(fǎng)問(wèn)技術(shù)中切換。

    小結(jié):Spring事務(wù)通過(guò)PlatformTransactionManager、TransactionDefinition和TransactionStatus接口統(tǒng)一事務(wù)管理API,并結(jié)合策略模式和模板方法決定具體實(shí)現(xiàn)。

    Spring事務(wù)API代碼還有個(gè)特點(diǎn)有沒(méi)有發(fā)現(xiàn),SQLException不見(jiàn)了。下面來(lái)看Spring事務(wù)是如何解決大量的異常處理代碼。

    2.2 大量的異常處理代碼

    為什么使用JDBC的代碼中會(huì)需要寫(xiě)這么多的異常處理代碼。這是因?yàn)镃onnection的每個(gè)方法都會(huì)拋出SQLException,而SQLException又是檢查異常,這就強(qiáng)制我們?cè)谑褂闷浞椒〞r(shí)必須進(jìn)行異常處理。那Spring事務(wù)是如何解決該問(wèn)題的。我們看下doCommit方法:

    protected?void?doCommit(DefaultTransactionStatus?status)?{DataSourceTransactionObject?txObject?=?(DataSourceTransactionObject)?status.getTransaction();Connection?con?=?txObject.getConnectionHolder().getConnection();if?(status.isDebug())?{logger.debug("Committing?JDBC?transaction?on?Connection?["?+?con?+?"]");}try?{con.commit();}catch?(SQLException?ex)?{//?異常轉(zhuǎn)換throw?new?TransactionSystemException("Could?not?commit?JDBC?transaction",?ex);} }

    Connection的commit方法會(huì)拋出檢查異常SQLException,在catch代碼塊中SQLException將被轉(zhuǎn)換成TransactionSystemException拋出,而TransactionSystemException是一個(gè)非檢查異常。通過(guò)將檢查異常轉(zhuǎn)換成非檢查異常,讓我們能夠自行決定是否捕獲異常,不強(qiáng)制進(jìn)行異常處理。

    Spring事務(wù)中幾乎為數(shù)據(jù)庫(kù)的所有錯(cuò)誤都定義了相應(yīng)的異常,統(tǒng)一了JDBC、Hibernate、MyBatis等不同異常API。這有助于我們?cè)谔幚懋惓r(shí)使用統(tǒng)一的異常API接口,無(wú)需關(guān)心具體的數(shù)據(jù)訪(fǎng)問(wèn)技術(shù)。

    小結(jié):Spring事務(wù)通過(guò)異常轉(zhuǎn)換避免強(qiáng)制異常處理。

    2.3 業(yè)務(wù)處理代碼與事務(wù)管理代碼混雜

    在2.1節(jié)中給出了使用Spring事務(wù)API的寫(xiě)法,即編程式事務(wù)管理,但仍未解決“業(yè)務(wù)處理代碼與事務(wù)管理代碼混雜”的問(wèn)題。這時(shí)候就可以利用Spring AOP將事務(wù)管理代碼這一橫切關(guān)注點(diǎn)從代碼中剝離出來(lái),即聲明式事務(wù)管理。以注解方式為例,通過(guò)為方法標(biāo)注@Transaction注解,將為該方法提供事務(wù)管理。其原理如下圖所示:

    聲明式事務(wù)原理

    Spring事務(wù)會(huì)為@Transaction標(biāo)注的方法的類(lèi)生成AOP增強(qiáng)的動(dòng)態(tài)代理類(lèi)對(duì)象,并且在調(diào)用目標(biāo)方法的攔截鏈中加入TransactionInterceptor進(jìn)行環(huán)繞增加,實(shí)現(xiàn)事務(wù)管理。

    下面我們看下TransactionInterceptor中的具體實(shí)現(xiàn),其invoke方法中將調(diào)用invokeWithinTransaction方法進(jìn)行事務(wù)管理,如下所示:

    protected?Object?invokeWithinTransaction(Method?method,?Class<?>?targetClass,?final?InvocationCallback?invocation)throws?Throwable?{//?查詢(xún)目標(biāo)方法事務(wù)屬性、確定事務(wù)管理器、構(gòu)造連接點(diǎn)標(biāo)識(shí)(用于確認(rèn)事務(wù)名稱(chēng))final?TransactionAttribute?txAttr?=?getTransactionAttributeSource().getTransactionAttribute(method,?targetClass);final?PlatformTransactionManager?tm?=?determineTransactionManager(txAttr);final?String?joinpointIdentification?=?methodIdentification(method,?targetClass,?txAttr);if?(txAttr?==?null?||?!(tm?instanceof?CallbackPreferringPlatformTransactionManager))?{//?創(chuàng)建事務(wù)TransactionInfo?txInfo?=?createTransactionIfNecessary(tm,?txAttr,?joinpointIdentification);Object?retVal?=?null;try?{//?通過(guò)回調(diào)執(zhí)行目標(biāo)方法retVal?=?invocation.proceedWithInvocation();}catch?(Throwable?ex)?{//?目標(biāo)方法執(zhí)行拋出異常,根據(jù)異常類(lèi)型執(zhí)行事務(wù)提交或者回滾操作completeTransactionAfterThrowing(txInfo,?ex);throw?ex;}finally?{//?清理當(dāng)前線(xiàn)程事務(wù)信息cleanupTransactionInfo(txInfo);}//?目標(biāo)方法執(zhí)行成功,提交事務(wù)commitTransactionAfterReturning(txInfo);return?retVal;}?else?{//?帶回調(diào)的事務(wù)執(zhí)行處理,一般用于編程式事務(wù)//?...} }

    在調(diào)用目標(biāo)方法前后加入了創(chuàng)建事務(wù)、處理異常、提交事務(wù)等操作。這讓我們不必編寫(xiě)事務(wù)管理代碼,只需通過(guò)@Transaction的屬性指定事務(wù)相關(guān)元信息。

    小結(jié):Spring事務(wù)通過(guò)AOP提供聲明式事務(wù)將業(yè)務(wù)處理代碼和事務(wù)管理代碼分離。

    ?

    3. 存在什么問(wèn)題

    Spring事務(wù)為了我們解決了第一節(jié)中列出的三個(gè)問(wèn)題,但同時(shí)也會(huì)帶來(lái)些新的問(wèn)題。

    3.1 非public方法失效

    @Transactional只有標(biāo)注在public級(jí)別的方法上才能生效,對(duì)于非public方法將不會(huì)生效。這是由于Spring AOP不支持對(duì)private、protect方法進(jìn)行攔截。從原理上來(lái)說(shuō),動(dòng)態(tài)代理是通過(guò)接口實(shí)現(xiàn),所以自然不能支持private和protect方法的。而CGLIB是通過(guò)繼承實(shí)現(xiàn),其實(shí)是可以支持protect方法的攔截的,但Spring AOP中并不支持這樣使用,筆者猜測(cè)做此限制是出于代理方法應(yīng)是public的考慮,以及為了保持CGLIB和動(dòng)態(tài)代理的一致。如果需要對(duì)protect或private方法攔截則建議使用AspectJ。

    3.2 自調(diào)用失效

    當(dāng)通過(guò)在Bean的內(nèi)部方法直接調(diào)用帶有@Transactional的方法時(shí),@Transactional將失效,例如:

    public?void?saveAB(A?a,?B?b) {saveA(a);saveB(b); }@Transactional public?void?saveA(A?a) {dao.saveA(a); }@Transactional public?void?saveB(B?b) {dao.saveB(b); }

    在saveAB中調(diào)用saveA和saveB方法,兩者的@Transactional都將失效。這是因?yàn)镾pring事務(wù)的實(shí)現(xiàn)基于代理類(lèi),當(dāng)在內(nèi)部直接調(diào)用方法時(shí),將不會(huì)經(jīng)過(guò)代理對(duì)象,而是直接調(diào)用目標(biāo)對(duì)象的方法,無(wú)法被TransactionInterceptor攔截處理。解決辦法:

    (1)ApplicationContextAware

    通過(guò)ApplicationContextAware注入的上下文獲得代理對(duì)象。

    public?void?saveAB(A?a,?B?b) {Test?self?=?(Test)?applicationContext.getBean("Test");self.saveA(a);self.saveB(b); }

    (2)AopContext

    通過(guò)AopContext獲得代理對(duì)象。

    public?void?saveAB(A?a,?B?b) {Test?self?=?(Test)AopContext.currentProxy();self.saveA(a);self.saveB(b); }

    (3)@Autowired

    通過(guò)@Autowired注解注入代理對(duì)象。

    @Component public?class?Test?{@AutowiredTest?self;public?void?saveAB(A?a,?B?b){self.saveA(a);self.saveB(b);}//?... }

    (4)拆分

    將saveA、saveB方法拆分到另一個(gè)類(lèi)中。

    public?void?saveAB(A?a,?B?b) {txOperate.saveA(a);txOperate.saveB(b); }

    上述兩個(gè)問(wèn)題都是由于Spring事務(wù)的實(shí)現(xiàn)方式的限制導(dǎo)致的問(wèn)題。下面再看兩個(gè)由于使用不當(dāng)容易犯錯(cuò)的兩個(gè)問(wèn)題。

    3.3 檢查異常默認(rèn)不回滾

    在默認(rèn)情況下,拋出非檢查異常會(huì)觸發(fā)回滾,而檢查異常不會(huì)。

    根據(jù)invokeWithinTransaction方法,我們可以知道異常處理邏輯在completeTransactionAfterThrowing方法中,其實(shí)現(xiàn)如下:

    protected?void?completeTransactionAfterThrowing(@Nullable?TransactionInfo?txInfo,?Throwable?ex)?{if?(txInfo?!=?null?&&?txInfo.getTransactionStatus()?!=?null)?{if?(logger.isTraceEnabled())?{logger.trace("Completing?transaction?for?["?+?txInfo.getJoinpointIdentification()?+"]?after?exception:?"?+?ex);}if?(txInfo.transactionAttribute?!=?null?&&?txInfo.transactionAttribute.rollbackOn(ex))?{try?{//?異常類(lèi)型為回滾異常,執(zhí)行事務(wù)回滾txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch?(TransactionSystemException?ex2)?{logger.error("Application?exception?overridden?by?rollback?exception",?ex);ex2.initApplicationException(ex);throw?ex2;}catch?(RuntimeException?|?Error?ex2)?{logger.error("Application?exception?overridden?by?rollback?exception",?ex);throw?ex2;}}else?{try?{//?異常類(lèi)型為非回滾異常,仍然執(zhí)行事務(wù)提交txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}catch?(TransactionSystemException?ex2)?{logger.error("Application?exception?overridden?by?commit?exception",?ex);ex2.initApplicationException(ex);throw?ex2;}catch?(RuntimeException?|?Error?ex2)?{logger.error("Application?exception?overridden?by?commit?exception",?ex);throw?ex2;}}} }

    根據(jù)rollbackOn判斷異常是否為回滾異常。只有RuntimeException和Error的實(shí)例,即非檢查異常,或者在@Transaction中通過(guò)rollbackFor屬性指定的回滾異常類(lèi)型,才會(huì)回滾事務(wù)。否則將繼續(xù)提交事務(wù)。所以如果需要對(duì)非檢查異常進(jìn)行回滾,需要記得指定rollbackFor屬性,不然將回滾失效。

    3.4 catch異常無(wú)法回滾

    在3.3節(jié)中我們說(shuō)到只有拋出非檢查異常或是rollbackFor中指定的異常才能觸發(fā)回滾。如果我們把異常catch住,而且沒(méi)拋出,則會(huì)導(dǎo)致無(wú)法觸發(fā)回滾,這也是開(kāi)發(fā)中常犯的錯(cuò)誤。例如:

    @Transactional public?void?insert(List<User>?users)?{try?{JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource);for?(User?user?:?users)?{String?insertUserSql?=?"insert?into?User?(id,?name)?values?(?,?)";jdbcTemplate.update(insertUserSql,?new?Object[]?{?user.getId(),user.getName()?});}}?catch?(Exception?e)?{e.printStackTrace();} }

    這里由于catch住了所有Exception,并且沒(méi)拋出。當(dāng)插入發(fā)生異常時(shí),將不會(huì)觸發(fā)回滾。

    但同時(shí)我們也可以利用這種機(jī)制,用try-catch包裹不用參與事務(wù)的數(shù)據(jù)操作,例如對(duì)于寫(xiě)入一些不重要的日志,我們可將其用try-catch包裹,避免拋出異常,則能避免寫(xiě)日志失敗而影響事務(wù)的提交。


    參考

  • Spring Framework Documentation——Data Access:?https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html

  • 《Spring揭秘》

  • 5-common-spring-transactional-pitfalls:?https://codete.com/blog/5-common-spring-transactional-pitfalls/

  • Spring事務(wù)原理一探:?https://zhuanlan.zhihu.com/p/54067384

  • 有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

    歡迎大家關(guān)注Java之道公眾號(hào)

    好文章,我在看??

    總結(jié)

    以上是生活随笔為你收集整理的三问Spring事务:解决什么问题?如何解决?存在什么问题?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。