事務(wù)中存在多線程,怎么處理?
在 Spring 框架中,@Transactional 注解作為一種聲明式事務(wù)管理的關(guān)鍵機(jī)制,其背后的工作原理遠(yuǎn)比簡單的 AOP(面向切面編程)和 ThreadLocal 存儲(chǔ)更為細(xì)膩。該注解的實(shí)現(xiàn)核心在于 Spring 的 TransactionInterceptor(事務(wù)攔截器)以及它如何與 Spring 的代理機(jī)制、TransactionManager(事務(wù)管理器)協(xié)同工作,來確保事務(wù)的開啟、提交或回滾等操作得以正確執(zhí)行。
一 注解解析與代理生成
當(dāng) Spring 容器初始化時(shí),會(huì)通過 AnnotationTransactionAttributeSource 掃描并識(shí)別出所有標(biāo)有 @Transactional 的方法。這些方法在被調(diào)用前,Spring 會(huì)根據(jù)配置(如基于接口或類的代理)為它們創(chuàng)建動(dòng)態(tài)代理對(duì)象。如果是基于接口的代理,則使用 JDK Dynamic Proxy;如果是基于類的,則采用 CGLIB。這個(gè)代理對(duì)象會(huì)在目標(biāo)方法調(diào)用前后插入事務(wù)處理邏輯。
1.1 Spring Bean的后處理器
一切始于 Spring 容器的 Bean 創(chuàng)建和初始化過程。Spring 通過一系列的 BeanPostProcessor 接口實(shí)現(xiàn)類來增強(qiáng) Bean 的功能,其中與事務(wù)管理密切相關(guān)的便是 AbstractAutoProxyCreator的子類,如 AnnotationAwareAspectJAutoProxyCreator。這個(gè)類負(fù)責(zé)掃描并創(chuàng)建代理對(duì)象,以便于在運(yùn)行時(shí)織入諸如事務(wù)管理這樣的切面邏輯。
1.2 識(shí)別 @Transactional 注解
- ClassPathScanningCandidateComponentProvider:Spring 首先會(huì)使用此類掃描指定包路徑下帶有特定注解(如@Transactional)的類或方法。
- AnnotationTransactionAttributeSource:一旦找到帶有 @Transactional 注解的類或方法,Spring 會(huì)使用 AnnotationTransactionAttributeSource 來解析這些注解,將其轉(zhuǎn)換為事務(wù)屬性(TransactionAttribute),比如事務(wù)的隔離級(jí)別、傳播行為、超時(shí)時(shí)間等。
1.3 創(chuàng)建代理對(duì)象
- 對(duì)于基于接口的代理,Spring 使用 JDK 的動(dòng)態(tài)代理技術(shù),通過JdkDynamicAopProxy創(chuàng)建代理對(duì)象。該代理會(huì)檢查調(diào)用鏈,并在調(diào)用目標(biāo)方法前插入事務(wù)管理的前置邏輯,調(diào)用后插入后置邏輯(如提交或回滾事務(wù))。
- 對(duì)于沒有實(shí)現(xiàn)接口的類,Spring 則利用 CGLIB 庫生成目標(biāo)類的子類作為代理,通過CglibAopProxy 來完成。CGLIB 代理同樣能夠在目標(biāo)方法調(diào)用的前后插入事務(wù)管理代碼。
1.4 TransactionInterceptor
- 在代理對(duì)象中,事務(wù)管理的具體邏輯是由 TransactionInterceptor(事務(wù)攔截器)來執(zhí)行的。它實(shí)現(xiàn)了 MethodInterceptor 接口,因此當(dāng)代理對(duì)象的方法被調(diào)用時(shí),會(huì)進(jìn)入invoke(MethodInvocation invocation) 方法。在這個(gè)方法內(nèi),TransactionInterceptor 根據(jù)解析出的事務(wù)屬性來決定是否開啟事務(wù)、使用何種傳播行為,并最終調(diào)用目標(biāo)方法。
總結(jié)下就是,Spring 通過 Bean 的后置處理器在容器初始化階段自動(dòng)檢測(cè)帶有 @Transactional 注解的類和方法,并通過動(dòng)態(tài)代理機(jī)制為這些組件創(chuàng)建代理對(duì)象。代理對(duì)象在方法調(diào)用時(shí),通過 TransactionInterceptor 這一核心組件,根據(jù)注解配置的事務(wù)屬性來管理事務(wù)生命周期,確保事務(wù)邏輯的無縫集成。
二 事務(wù)攔截與執(zhí)行
2.1 TransactionInterceptor 的作用
TransactionInterceptor 實(shí)現(xiàn)了 Spring 的 MethodInterceptor 接口,這意味著它能夠攔截方法調(diào)用,并在調(diào)用前后執(zhí)行額外的邏輯,即事務(wù)管理邏輯。其核心職責(zé)包括:
- 識(shí)別是否需要事務(wù): 根據(jù)目標(biāo)方法上的 @Transactional 注解(如果有的話)及其屬性(如傳播行為、隔離級(jí)別、超時(shí)時(shí)間等),決定是否需要啟動(dòng)一個(gè)新的事務(wù)或加入到現(xiàn)有的事務(wù)中。需要注意的是,事務(wù)上下文(包括連接信息等)被存儲(chǔ)在由 Spring 管理的一個(gè)特定的 TransactionSynchronizationManager 中,TransactionSynchronizationManager 內(nèi)部使用了 ThreadLocal。
- 事務(wù)管理: 在方法調(diào)用之前開啟事務(wù),調(diào)用之后根據(jù)方法執(zhí)行結(jié)果提交或回滾事務(wù)。這包括異常處理邏輯,即在遇到未被捕獲的異常時(shí),確保事務(wù)被正確回滾。
2.2 攔截與執(zhí)行流程
- 方法調(diào)用前:
- 事務(wù)屬性解析: TransactionInterceptor 首先通過TransactionAttributeSource(通常是AnnotationTransactionAttributeSource)獲取目標(biāo)方法的事務(wù)屬性。
- 事務(wù)開始: 根據(jù)解析出的事務(wù)屬性,決定是否創(chuàng)建新的事務(wù)或者加入到當(dāng)前事務(wù)中。如果需要新事務(wù),它會(huì)通過事務(wù)管理器(如 PlatformTransactionManager 的實(shí)現(xiàn)類)來開始事務(wù)。
- 方法執(zhí)行:
在事務(wù)上下文中執(zhí)行實(shí)際的目標(biāo)方法。此時(shí),如果目標(biāo)方法內(nèi)部拋出了異常,這個(gè)異常會(huì)被暫存以供后續(xù)處理。
方法調(diào)用后:
異常處理: 如果方法執(zhí)行過程中拋出了異常,TransactionInterceptor 會(huì)捕獲到這個(gè)異常,并根據(jù)異常類型及事務(wù)屬性決定是否需要回滾事務(wù)。通常,非檢查型異常(繼承自 RuntimeException 的異常)會(huì)導(dǎo)致事務(wù)回滾,而檢查型異常則不會(huì),除非事務(wù)屬性特別指定了回滾規(guī)則。
事務(wù)提交或回滾: 如果方法正常結(jié)束,或者按事務(wù)屬性應(yīng)該提交事務(wù)的情況下,事務(wù)管理器會(huì)提交事務(wù)。相反,如果需要回滾,則執(zhí)行回滾操作。
資源清理: 在事務(wù)結(jié)束后,確保所有資源被正確釋放,比如關(guān)閉數(shù)據(jù)庫連接等。
2.3 動(dòng)態(tài)代理的作用
整個(gè)過程中,動(dòng)態(tài)代理扮演著關(guān)鍵角色。無論是 JDK 動(dòng)態(tài)代理還是 CGLIB 代理,它們都是在調(diào)用真正業(yè)務(wù)方法之前插入事務(wù)攔截邏輯的橋梁。當(dāng)客戶端代碼調(diào)用代理對(duì)象上的方法時(shí),實(shí)際上是調(diào)用了由 TransactionInterceptor 所控制的代理邏輯,從而透明地在業(yè)務(wù)邏輯執(zhí)行前后管理事務(wù)。
通過這種方式,開發(fā)者無需在每個(gè)需要事務(wù)管理的方法中手動(dòng)編寫開啟、提交或回滾事務(wù)的代碼,極大地簡化了開發(fā)復(fù)雜度,提高了代碼的可維護(hù)性和可讀性。
三 多線程環(huán)境下的挑戰(zhàn)
當(dāng) @Transactional 標(biāo)記的方法內(nèi)部啟動(dòng)新的線程時(shí),問題就顯現(xiàn)了。前面提到,事務(wù)攔截使用了 TransactionInterceptor,而 TransactionInterceptor 內(nèi)部用到了 TransactionSynchronizationManager,TransactionSynchronizationManager 使用 ThreadLocal 來存儲(chǔ)事務(wù)相關(guān)的資源信息,如數(shù)據(jù)庫連接、JMS 會(huì)話等。這意味著每個(gè)線程都有其獨(dú)立的事務(wù)上下文,確保了事務(wù)信息在線程間的隔離。
換句話說,Spring 管理的事務(wù)上下文是基于調(diào)用線程的,新線程并沒有繼承原線程的 TransactionSynchronizationManager 中的事務(wù)上下文。因此,新線程執(zhí)行的數(shù)據(jù)庫操作實(shí)際上是在無事務(wù)管理的環(huán)境下進(jìn)行的,這就導(dǎo)致事務(wù)失效。
那如果非要用多線程怎么辦呢?這個(gè)時(shí)候可以使用編程式事務(wù),首先設(shè)置一個(gè)全局變量 Boolean,默認(rèn)是可以提交的 true,在子線程,通過編程式事務(wù)的方式去開啟事務(wù),然后插入數(shù)據(jù),插入完成后,事務(wù)先不提交,同時(shí)通知主線程,我準(zhǔn)備好了,進(jìn)入等待狀態(tài)。如果子線程出現(xiàn)異常,那就通知主線程,我這邊發(fā)生異常,然后自己回滾。
最后主線程收集各個(gè)子線程的狀態(tài),如果有一個(gè)線程出現(xiàn)問題,那么全局變量就設(shè)置為不可提交false,然后喚醒所有子線程,進(jìn)行回滾。
這里涉及到線程同步可以利用閉鎖去實(shí)現(xiàn);當(dāng)主線程通知各個(gè)子線程提交事務(wù)的時(shí)候,子線程可能在提交的時(shí)候出錯(cuò)了,最終導(dǎo)致數(shù)據(jù)不一致,那么這種時(shí)候可能就需要引入重試機(jī)制,有了重試機(jī)制后,又要去保證冪等性等等。
這一套方案下來大伙有沒有覺得眼熟,是不是就是分布式事務(wù)的處理思路了?
所以說,非要在事務(wù)中開啟多線程也沒問題,但是不建議這么做。