面試官:@Transactional 與 @Async 可以同時(shí)使用嗎
@Transactional 能與 @Async 同時(shí)使用嗎,相信大部分人都回答不上來,畢竟這倆一起使用,這場(chǎng)景......真不一定有幾個(gè)人用過。
一、@Transactional 與 @Async 可以同時(shí)使用嗎
首先我們還是先寫個(gè) demo 看看運(yùn)行結(jié)果。
demo 地址:https://github.com/zuiyu-main/EncryptDemo/tree/mysql-transactional-async
代碼如下,使用注入的方式調(diào)用 TestService#test 方法。TestService#test 方法上同時(shí)使用 @Async 與 @Transactional 注解標(biāo)注。
注意:需要在啟動(dòng)類中加入@EnableAsync注解,不了解的可以看下上一篇 @Async 原理。
讓我們先看下運(yùn)行結(jié)果。
通過上圖可以知道,方法已經(jīng)在異步線程中執(zhí)行,方法的異常也已經(jīng)拋出,數(shù)據(jù)庫中也沒有插入對(duì)應(yīng)的數(shù)據(jù)。得出結(jié)論,@Transactional 可以與 @Async 注解同時(shí)使用,且事務(wù)可以生效。
這里提前透漏一個(gè)知識(shí)點(diǎn)把,大家可以把斷點(diǎn)打在TransactionAspectSupport#invokeWithinTransaction()方法中243行位置處,判斷代碼是走245行的回滾邏輯還是258行的提交邏輯。
在或者在PlatformTransactionManager接口的 commit 或者 rollback 打上斷點(diǎn),看執(zhí)行哪個(gè)方法。
二、@Transactional 注解原理
上一篇文章中說過了 @Async 注解的原理,還沒看過的小伙伴可以過去看一眼哦。@Transactional 注解的工作原理也類似,都是 AOP 實(shí)現(xiàn)。
在哪找入口呢,@Async 注解的學(xué)習(xí)過程中,我們是從注解入手的,@Transactional 同樣也可以,如下圖所示。
所以上文中提到驗(yàn)證事務(wù)是否生效的方式中我就是從這受啟發(fā)的。
不過今天我們看另一種方式,就是通過斷點(diǎn)查看調(diào)用棧的方式。
這種方式有個(gè)缺點(diǎn),就是假如事務(wù)是沒有生效的,也就是說沒有被 spring 代理,此時(shí)是無法達(dá)到我們的預(yù)期的。
所以有的時(shí)候看事務(wù)有沒有生效,也可以通過這個(gè)方式。
首先在我們的異步方法第一行打入斷點(diǎn)。
通過這一個(gè)斷點(diǎn),我們就可以知道兩個(gè)消息。
- 代碼1處,使用了Async異步執(zhí)行,看了上篇文章@Async原理的一看就知道 @Async 注解生效了。
- 代碼2處,第一處發(fā)現(xiàn)Transaction字眼的地方,所以也可以說明,我們這個(gè)方法已經(jīng)被 spring 的事務(wù)管理起來了,而事務(wù)的入口就在這TransactionInterceptor#invoke,在該方法內(nèi)部又調(diào)用了 TransactionAspectSupport#invokeWithinTransaction 。
進(jìn)入到方法內(nèi)部,需要注意的是 tas.getTransactionAttribute()。如果這個(gè) transaction attribute 是空,說明這個(gè)方法是沒有被事務(wù)管理的。
在 tas.getTransactionAttribute() 中,執(zhí)行的是 AbstractFallbackTransactionAttributeSource 下的 getTransactionAttribute 方法,而在 getTransactionAttribute 方法中有一行 computeTransactionAttribute 方法,在該方法中有如下這樣一行代碼,這也是事務(wù)失效場(chǎng)景之一的 priviate 方法不支持事務(wù)。
事務(wù)失效場(chǎng)景:private 方法不支持事務(wù)。
如果你在 debug 時(shí)沒有攔住該方法,需要重啟,在程序啟動(dòng)時(shí)會(huì)緩存該方法的事務(wù)屬性。
我們繼續(xù)回到 TransactionAspectSupport#invokeWithinTransaction 方法,繼續(xù)往下走。
在回滾事務(wù)的方法中,進(jìn)行了判斷,這就是另一種事務(wù)失效的場(chǎng)景,異常不匹配。所以在使用事務(wù)注解時(shí)一定要注意。
事務(wù)失效場(chǎng)景:異常類型不匹配。
當(dāng)我們沒有指定異常類型時(shí),默認(rèn)只對(duì) RuntimeException 和 Error 有效。
所以,我們總結(jié)一下,與 一文搞懂@Async 原理一起閱讀更佳。
- 程序啟動(dòng)之后為加了@Transactional 注解的方法緩存事務(wù)相關(guān)屬性。
- 當(dāng)方法中同時(shí)使用 @Async 與 @Transactional 注解時(shí),@Async的優(yōu)先級(jí)更高,會(huì)在 @Async 異步的線程中進(jìn)行事務(wù)管理,當(dāng)發(fā)生異常時(shí)也是可以回滾的。
現(xiàn)在我們知道了 @Transactional 與 @Async 注解的原理,那么當(dāng)他們兩個(gè)互不相干的方法互相調(diào)用還會(huì)生效嗎?
只驗(yàn)證注入方式調(diào)用的形式。
三、@Transactional 調(diào)用 @Async
結(jié)論:test方法中事務(wù)生效,異步方法 test2 事務(wù)不生效。
異步方法 test2 上也加入事務(wù)注解
結(jié)論:test方法與 test2方法兩個(gè)獨(dú)立的事務(wù),互不相關(guān)。
四、@Async 調(diào)用 @Transactional
結(jié)論:test 方法會(huì)因?yàn)?test2 方法執(zhí)行報(bào)錯(cuò)而異常終止,test2 方法事務(wù)回滾。
如果我們把 test 方法中插入數(shù)據(jù)的代碼移動(dòng)到調(diào)用 test2 方法之前。
結(jié)論:test 方法中數(shù)據(jù)插入成功,test2 數(shù)據(jù)插入失敗,事務(wù)回滾。
總結(jié)
我們?cè)谏弦黄恼轮袑W(xué)習(xí)了 @Async 注解的原理,今天看了 @Transactional 注解的原理。
通過寫代碼的形式,驗(yàn)證了:
- 當(dāng)方法中同時(shí)使用 @Transactional 與 @Async 時(shí),事務(wù)是可以生效的。
- @Transactional 調(diào)用 @Async 的方式,異步方法的事務(wù)是無法生效的。
- @Async 調(diào)用 @Transaction 的方式,異步方法事務(wù)是可以生效的,需要注意的是調(diào)用方也是沒有事務(wù)管理的。