淺析Spring的事務(wù)實(shí)現(xiàn)原理
SQL事務(wù)實(shí)現(xiàn)簡(jiǎn)介
首先我們來(lái)了解下,最簡(jiǎn)單的事務(wù)是怎么實(shí)現(xiàn)的呢?以JDBC為例,當(dāng)一個(gè)數(shù)據(jù)庫(kù)Connection對(duì)象創(chuàng)建后,其會(huì)默認(rèn)自動(dòng)提交事務(wù);每次執(zhí)行SQL語(yǔ)句時(shí),如果成功,就會(huì)向數(shù)據(jù)庫(kù)自動(dòng)提交,不能回滾。
通過(guò)調(diào)用setAutoCommit(false)方法可以取消自動(dòng)提交事務(wù)。等到所有的SQL語(yǔ)句都執(zhí)行成功后,調(diào)用commit()方法提交事務(wù)。如果其中某個(gè)操作失敗或出現(xiàn)異常時(shí),則調(diào)用rollback()方法回滾事務(wù)。具體代碼如下所示:
public void noTransaction() { Connection connection = null; String sql = "update account set balance=balance-100 where id=1"; String sql2 = "update account set balance=balance+100 where id=2"; //創(chuàng)建PreparedStatement對(duì)象 PreparedStatement preparedStatement = null; try { connection = JDBCUtils.getConnection();// 獲取數(shù)據(jù)庫(kù)連接 connection.setAutoCommit(false);//事務(wù)開(kāi)始 preparedStatement = connection.prepareStatement(sql); preparedStatement.executeUpdate();//執(zhí)行第一個(gè)sql preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate();//執(zhí)行sql2 //提交事務(wù) connection.commit(); } catch (SQLException e) { //進(jìn)行事務(wù)回滾,默認(rèn)回滾到事務(wù)開(kāi)始的地方 try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { //關(guān)閉流 JDBCUtils.close(null, preparedStatement, connection); } }
將代碼抽象成執(zhí)行步驟,主要有以下四步:
- 獲取Mysql鏈接
- 執(zhí)行SQL語(yǔ)句
- 提交SQL事務(wù)
- 存在異常則做Mysql的事務(wù)回滾。
可以發(fā)現(xiàn),常規(guī)情況下只有執(zhí)行SQL語(yǔ)句的內(nèi)容存在差異。如果能將相同部分抽取出來(lái),接入方接入時(shí)只考慮SQL語(yǔ)句內(nèi)容,就可以減少接入的成本。同時(shí)觀察到抽取的部分處于執(zhí)行SQL語(yǔ)句的前后,那么很自然的就可以想到兩種解決方案:
1、在JAVA8中,提供了函數(shù)式編程。我們可以將要執(zhí)行的SQL語(yǔ)句封裝成函數(shù),作為入?yún)魅氩?zhí)行。
2、采用動(dòng)態(tài)代理對(duì)執(zhí)行SQL的前后做增強(qiáng)。
編程式事務(wù)
Spring中采用函數(shù)式編程實(shí)現(xiàn)的事務(wù),被稱為編程式事務(wù)。編程式事務(wù)的實(shí)現(xiàn)相對(duì)簡(jiǎn)單,主要由類TransactionTemplate負(fù)責(zé)實(shí)現(xiàn)。具體代碼可以見(jiàn)如下所示:
@Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else { //獲取事務(wù) TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { //執(zhí)行SQL語(yǔ)句內(nèi)容 result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { //異常回滾 rollbackOnException(status, ex); throw ex; } catch (Throwable ex) { // 異常回滾 rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } //提交事務(wù) this.transactionManager.commit(status); return result; } }
TransactionCallBack作為入?yún)魅?,其中就主要是我們要?zhí)行的SQL語(yǔ)句內(nèi)容。而其余部分可以看到,其實(shí)就和我們前面所描述的四步基本相似:
- 獲取Mysql鏈接
- 執(zhí)行SQL語(yǔ)句
- 提交SQL事務(wù)
- 存在異常則做Mysql的事務(wù)回滾。
聲明式事務(wù)
在Spring中,采用AOP做增強(qiáng)邏輯的被稱為聲明式事務(wù)。相比起編程式事務(wù),聲明式事務(wù)相對(duì)復(fù)雜。因此,在了解聲明式事務(wù)之前,我們需要先簡(jiǎn)單了解一下Spring是如何支持AOP(動(dòng)態(tài)代理)。首先我們知道,Spring中Bean的存在形式有以下幾個(gè)階段:
其中非常關(guān)鍵點(diǎn)就在BeanFactory。當(dāng)我們對(duì)一個(gè)Bean定義代理對(duì)象后,BeanFactory生成的就不會(huì)是單純的Bean實(shí)例對(duì)象,而是Bean的動(dòng)態(tài)代理。通過(guò)調(diào)用Bean的動(dòng)態(tài)代理中的方法,來(lái)實(shí)現(xiàn)AOP。那么如何自定義自己的AOP呢?要實(shí)現(xiàn)AOP需要明確兩個(gè)點(diǎn):
1、需要在哪里做增強(qiáng)?(定義切點(diǎn))
2、需要做什么樣的增強(qiáng)邏輯?(定義增強(qiáng)邏輯)
對(duì)于這兩點(diǎn),Spring主要通過(guò)**事務(wù)代理管理配置類(ProxyTransactionManagementConfiguration)**進(jìn)行實(shí)現(xiàn)。
從類圖中可以看到,事務(wù)代理管理配置類主要定義了三個(gè)Bean對(duì)象:
- 注釋事務(wù)屬性源(AnnotationTransactionAttributeSource),其主要負(fù)責(zé)判斷當(dāng)前類是否為需要增強(qiáng)的類,即"哪里需要做增強(qiáng)"。
- 事務(wù)攔截器(TransactionInterceptor),該類主要負(fù)責(zé)對(duì)事務(wù)做鏈接獲取、事務(wù)提交以及事務(wù)回滾。即"怎么做增強(qiáng)"。
- Bean工廠事務(wù)屬性源指導(dǎo)(BeanFactoryTransactionAttributeSourceAdvisor),這個(gè)與事務(wù)本身無(wú)關(guān),主要是在Bean工廠生產(chǎn)Bean實(shí)例的時(shí)候,方便對(duì)Bean進(jìn)行替換使用的。其中主要是負(fù)責(zé)將定義的切點(diǎn)和增強(qiáng)邏輯注入到Spring中。
這里我們逐一來(lái)介紹這三個(gè)Bean對(duì)象。
注釋事務(wù)屬性源
"哪里需要做增強(qiáng)",意味著類要具備判斷是否需增強(qiáng)的能力。為此,注釋事務(wù)屬性源提供了一個(gè)關(guān)鍵的方法:isCandidateClass()。
? 但聲明事務(wù)的注解一定不只一種。如果需要識(shí)別所有包下的事務(wù)型注解,一定會(huì)需要多次判斷。因此,在注解事務(wù)屬性源中,還保存了一組接口對(duì)象事務(wù)注釋解析器(TransactionAnnotationParser),通過(guò)循環(huán)遍歷這組事務(wù)注釋解析器,就可以對(duì)不同框架注解進(jìn)行處理。具體源碼如下:
@Override public boolean isCandidateClass(Class<?> targetClass) { for (TransactionAnnotationParser parser : this.annotationParsers) { if (parser.isCandidateClass(targetClass)) { return true; } } return false; }
以SpringTransactionAnnotationParser注釋解析器為例,其實(shí)現(xiàn)的isCandidateClass()方法判斷類是否被@Transactional類注釋了,如果是,那么該類就是潛在的候選類。
@Override public boolean isCandidateClass(Class<?> targetClass) { return AnnotationUtils.isCandidateClass(targetClass, Transactional.class); }
依次類推,對(duì)@TransactionAttribute等其他框架的注釋,我們都可以采用這樣方法實(shí)現(xiàn)。
事務(wù)攔截器
具備了判斷哪些類需要執(zhí)行事務(wù)的能力后,我們還需要確定具體的增強(qiáng)邏輯是什么樣子的。而這就是事務(wù)攔截器主要功能。要實(shí)現(xiàn)這個(gè)功能,需要在對(duì)應(yīng)方法被調(diào)用時(shí),執(zhí)行增強(qiáng)方法。
從類圖首先可以看到,為了能夠察覺(jué)到方法的調(diào)用,事務(wù)攔截器實(shí)現(xiàn)了方法攔截器接口(MethodInterceptor)的invoke方法,在invoke方法中先判斷當(dāng)前執(zhí)行的方法屬于哪個(gè)類,緊接著會(huì)用invokeWithinTransaction()對(duì)方法進(jìn)行事務(wù)性的包裝。其源碼如下:
@Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { // 判斷執(zhí)行的方法屬于哪個(gè)類 Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); //再調(diào)用事務(wù)進(jìn)行執(zhí)行 return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() { @Override @Nullable public Object proceedWithInvocation() throws Throwable { return invocation.proceed(); } @Override public Object getTarget() { return invocation.getThis(); } @Override public Object[] getArguments() { return invocation.getArguments(); } }); }
主要邏輯放在invokeWithinTransaction()方法中。在該方法中,主要考慮了三類不同的編程方式的事務(wù),分別是:響應(yīng)式事務(wù)(ReactiveTransactionManager)、回調(diào)優(yōu)先型事務(wù)(CallbackPreferringPlatformTransactionManager)和非回調(diào)優(yōu)先型事務(wù)(非CallbackPreferringPlatformTransactionManager)。
三者的差異主要在于:
1、響應(yīng)式編程常采用Mono或Flux實(shí)現(xiàn),需要對(duì)兩種方式選擇相應(yīng)適配器做適配。
2、后兩者從名字上可以看出差異,回調(diào)型優(yōu)先的事務(wù),會(huì)先執(zhí)行回調(diào)再執(zhí)行事務(wù)。而非回調(diào)優(yōu)先型事務(wù),則關(guān)注于事務(wù)的執(zhí)行,至于回調(diào)的失敗與否不需要影響事務(wù)的回滾。
盡管三者存在一些差異,但他們對(duì)于事務(wù)的實(shí)現(xiàn)其實(shí)是相似的,這里以非回調(diào)優(yōu)先型事務(wù)為例子:
@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); final TransactionManager tm = determineTransactionManager(txAttr); ....... PlatformTransactionManager ptm = asPlatformTransactionManager(tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // 創(chuàng)建事務(wù) TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { // 執(zhí)行方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 回滾處理 + 拋出異常終止執(zhí)行 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } // 正常執(zhí)行了事務(wù),此時(shí)再執(zhí)行回調(diào) if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { TransactionStatus status = txInfo.getTransactionStatus(); if (status != null && txAttr != null) { retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } } // 提交事務(wù) commitTransactionAfterReturning(txInfo); return retVal; } }
源碼本身不復(fù)雜,可以看到也是四步:
- 獲取Mysql鏈接信息
- 執(zhí)行SQL語(yǔ)句
- 提交SQL事務(wù)
- 存在異常則做Mysql的事務(wù)回滾。
Bean工廠事務(wù)屬性源指導(dǎo)
對(duì)于Bean工廠事務(wù)屬性源指導(dǎo),其主要負(fù)責(zé)用于定義切點(diǎn)和增強(qiáng)邏輯,并將這些事務(wù)的邏輯注冊(cè)到Spring中用于實(shí)現(xiàn)。如下是Bean工廠事務(wù)屬性源指導(dǎo)的類圖。
從類圖上可以看到,其繼承了AbstractPointcutAdvisor關(guān)鍵模版類,該類是Spring中用于定義切點(diǎn)和增強(qiáng)邏輯。通過(guò)指定PointCut和Advice,就可以實(shí)現(xiàn)自定義的增強(qiáng)邏輯。因此,Bean工廠事務(wù)屬性源指導(dǎo)只要將事務(wù)攔截器標(biāo)記為增強(qiáng)邏輯,將注釋事務(wù)屬性源標(biāo)記為切點(diǎn),就可以讓其在Spring中作為AOP生效。
通過(guò)這三者的合作:注釋事務(wù)屬性源標(biāo)注了切點(diǎn)(說(shuō)明我那些方法需要做增強(qiáng));事務(wù)攔截器定義了要執(zhí)行的增強(qiáng)邏輯(說(shuō)明我對(duì)這些方法怎么做增強(qiáng));Bean工廠事務(wù)屬性源指導(dǎo)則將切點(diǎn)和增強(qiáng)邏輯注入到Spring中使其生效。從而實(shí)現(xiàn)了Spring的聲明式事務(wù)的內(nèi)容。
事務(wù)多樣性支持
在前述內(nèi)容中,我們思考了SQL情況下如何實(shí)現(xiàn)事務(wù)。但有個(gè)問(wèn)題,如果數(shù)據(jù)源換成Redission、換成分布式事務(wù)的API,代碼還能快速?gòu)?fù)用么?簡(jiǎn)而言之,Spring是如何支持?jǐn)?shù)據(jù)源多樣性?如何確保新數(shù)據(jù)源的快速接入?
對(duì)實(shí)現(xiàn)事務(wù)的流程做進(jìn)一步抽象,不難發(fā)現(xiàn)一次事務(wù)中,框架需要關(guān)注的功能其實(shí)只有三個(gè):
- 獲取事務(wù)鏈接
- 提交事務(wù)
- 事務(wù)回滾
因此,對(duì)不同的數(shù)據(jù)源,都可以將其抽象成這三個(gè)能力。應(yīng)用層只需要對(duì)這三個(gè)能力進(jìn)行調(diào)用,就不會(huì)在因?yàn)橄聦訑?shù)據(jù)源的差異而需要大幅度的改動(dòng)。而這正與面向接口設(shè)計(jì)的思想不謀而合
為此,Spring專門(mén)設(shè)計(jì)了接口PlatformTransactionManager,其主要負(fù)責(zé)對(duì)外提供三個(gè)方法:getTransaction(definition)、commit(status)、rollback(status)。就用來(lái)抽象的上述的三個(gè)功能。由此一來(lái),應(yīng)用層的代碼實(shí)現(xiàn)類(這里以TransactionTemplate為例子)就不再需要依賴于我的數(shù)據(jù)源究竟是JDBC、Redission還是DataSource。面對(duì)抽象編程,從而減少了接入需要考慮不同類型所帶來(lái)的成本。
總結(jié)
本文介紹了Spring中針對(duì)SQL事務(wù)實(shí)現(xiàn)的兩種方式:編程式事務(wù)和聲明式事務(wù)。同時(shí)介紹了對(duì)于多種不同的數(shù)據(jù)源,Spring在設(shè)計(jì)上的架構(gòu)實(shí)現(xiàn),希望對(duì)大家后續(xù)的開(kāi)發(fā)設(shè)計(jì)有所幫助。
以上就是淺析Spring的事務(wù)實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Spring事務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springMVC攔截器HandlerInterceptor用法代碼示例
這篇文章主要介紹了springMVC攔截器HandlerInterceptor用法代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-11-11100-200之間所有素?cái)?shù)求和程序代碼(二個(gè)版本)
寫(xiě)一個(gè)求100-200之間素?cái)?shù),并求和的程序,大家參考使用吧2013-11-11Java對(duì)時(shí)間的簡(jiǎn)單操作實(shí)例
這篇文章主要介紹了Java對(duì)時(shí)間的簡(jiǎn)單操作,實(shí)例分析了針對(duì)java.util.Date的各類常見(jiàn)操作,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01解決restlet client報(bào)錯(cuò)No response.Is the cer
這篇文章主要介紹了解決restlet client報(bào)錯(cuò)No response.Is the certificate valid? Click here to check.問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01