歡迎光臨
每天分享高質量文章

【面試】足夠“忽悠”面試官的『Spring事務管理器』原始碼閱讀梳理(建議珍藏)

來自:程式設計新說

 

PS:文章內容涉及原始碼,請耐心閱讀。

理論實踐,相輔相成

實踐出真知。這是無比正確的。但是也會很辛苦。

就像淘金一樣,從大量沙子中淘出金子一定是一個無比艱辛的過程。但如果真能淘出來,也一定是像金子一樣寶貴的東西。

他老人家還說過,當真知上升為理論的時候,就可以反過來指導實踐了。

在當下這個時代,前輩們已經發現和整理了很多理論,我們直接拿來使用就行了。“拿來主義”不全是不好的。

如果說閱讀原始碼算一種實踐的話,那我們拿什麼“理論”來指導它呢?

兵馬未動,糧草先行

 


答案自然是官方檔案。官方檔案就是前輩們總結出來的“理論”,一般來說包含三方面的內容:

a)哲學方面:一些設計思想,比如初衷啊、靈感來源啊這些。一些取捨的選擇,比如主要是為了剋服什麼痛點、解決什麼問題。

b)詳細解說:把整體內容一點一點的講清楚,包括很多名字解釋,很多設計原理,還有很多註意事項等。

c)入門示例:一些簡單的常規使用例子。


下麵我們就來熟悉下這些“理論”的關鍵部分:

事務的執行是和執行緒相關的,那是不是就要使用ThreadLocal來儲存一些相關東西,究竟會儲存哪些東西呢。

物理事務就是到資料庫的一個物理連結,這個連結一開始是如何建立,建立好後又是如何儲存起來呢。

邏輯事務就是一個帶有事務註解的方法,它需要關聯到一個物理事務上。那它是不是先從當前背景關係尋找物理事務,找到就用,否則就新開一個物理事務呢。

多個邏輯事務可以對映到一個物理事務上,邏輯事務是各自提交的,如何處理邏輯事務提交和物理事務提交間的關係呢,至少所有的邏輯事務都提交了才可以提交物理事務。

獲取事務時的引數叫事務定義,是一個介面,那它的實現類是哪個,都會包含哪些內容呀,至少要包含事務註解裡指定的內容吧。

獲取事務時的結果叫事務狀態,是一個介面,那它的實現類是哪個,都會包含哪些內容呀。

獲取事務這個方法是非常核心的方法,入參和出參分別是事務定義和事務狀態。你會不會感到有些奇怪,獲取事務的結果不應該是一個事務嗎,為啥卻是一個事務狀態呢?到底有沒有一個類和事務這兩個字對應呢?

多個邏輯事務也可以對映到多個物理事務上,此時就會遇到在當下已存在物理事務的時候再開啟新的物理事務。那麼就需要將當下事務掛起,具體的掛起會執行哪些操作呢。

掛起的事務會存到那裡呢,在新的物理事務提交完畢後又如何將掛起的事務恢復呢。

以上這些問題在你多次閱讀檔案和思考後會自動出現的,那我們就帶著這些問題去原始碼中尋找答案。

冥冥之中,早已註定

就像上篇文章中說的,原始碼閱讀找到入口點非常重要。這裡的入口點自然就是事務管理器了。如下圖:

 


這個介面很重要但卻非常簡單,就三個方法,獲取事務/提交/回滾。但它的實現卻相對比較複雜,也讓我們認識到,介面的定義是一個抽象的事情,可以不用考慮實現。

它的一個抽象實現類就是AbstractPlatformTransactionManager,這個類實現了事務管理的整個邏輯關係流程,但是把涉及和具體事務打交道的東西又定義為抽象方法讓子類去實現。

那麼對應於單個資料庫事務的具體實現類就是DataSourceTransactionManager,這個類會完成這些和事務相關的具體的操作。

獲取事務方法的整體執行流程如下:

 

 

1、呼叫doGetTransaction()方法從當前背景關係中獲取事務物件transaction。

2、如果事務已經存在:

2.1、如果此時事務傳播特性是NEVER,則丟擲異常。

2.2、如果此時事務的傳播特性是NOT_SUPPORTED,則呼叫suspend(transaction)掛起當前事務,將被掛起的資源suspendedResources放入事務狀態裡。

2.3、如果此時事務狀態是REQUIRES_NEW,則呼叫suspend(transaction)掛起當前事務,將事務物件transaction和被掛起的資源suspendedResources放入事務狀態裡。然後呼叫doBegin(transaction, definition)方法去真正開啟事務。最後呼叫prepareSynchronization(status, definition)方法準備一下事務同步。

2.4、如果此時事務的傳播特性是NESTED,又分三種情況:

2.4.1、如果不允許巢狀事務,直接丟擲異常。

2.4.2、如果使用儲存點(Savepoint)來實現巢狀事務,那直接使用當前事務,建立一個儲存點就可以了。

2.4.3、如果使用新的事務來實現巢狀事務,那就呼叫doBegin(transaction, definition)開啟新的事務,此時不需要掛起當前事務。

2.5、對於剩下三種傳播特性REQUIRED/MANDATORY/SUPPORTS,則不需要建立新事務,直接使用當前事務就可以了。

3、如果事務不存在:

3.1、如果此時事務的傳播特性是MANDATORY,則會丟擲異常。

3.2、如果此時事務的傳播特性是REQUIRED/REQUIRES_NEW/NESTED,則呼叫suspend(null)掛起當前事務,將事務物件transaction和被掛起的資源suspendedResources都放入事務狀態裡。然後呼叫doBegin(transaction, definition)方法去真正開啟事務。最後呼叫prepareSynchronization(status, definition)方法準備一下事務同步。

3.3、對於剩下的三種傳播特性SUPPORTS/NOT_SUPPORTED/NEVER,則不需要操作事務。


獲取事務物件的方法如下圖:

 

 

一共有三個關註點:

1、事務物件,即和事務這兩個字對應的那個類。DataSourceTransactionObject從類名來看就是採用資料源實現的事務物件。下麵是它的欄位:

 

可以看到這個事務物件最主要的作用就是儲存了一個ConnectionHolder物件。

2、使用DataSource物件獲取一個ConnectionHolder物件,從哪裡獲取呢?自然是當前執行的背景關係,即ThreadLocal裡了。如下圖:

 

 

此圖中有三點需要註意:

2.1、事務性資源是儲存在Map裡,key就是DataSource物件,value就是ConnectionHolder物件。2.2、事務同步這個集合Set(圖中黑色線框那個)只有在多個資料源的分散式事務時才使用。2.3、剩下的是四個和事務相關的變數,事務名稱/是否只讀/隔離級別/是否啟用。

3、將上一步獲取的ConnectionHolder物件(也可能是null)放入事務物件中。


請看ConnectionHolder的定義,如下圖:

 

 

主要關註兩點:

一是它包含一個資料庫連結(Connection),因此可以認為它就是表示一個物理事務。

二是它包含一個取用計數(referenceCount),來指示它被取用次數,表示當前有多少個邏輯事務關聯到它(在單資料源時並沒有使用該欄位)。

 

接著是判斷一個事務是否已存在,如下圖:

 

 

即事務物件要關聯到一個物理事務(即有一個ConnectionHolder物件),同時物理事務還必須是活動的。

那麼在首次執行時,事務肯定是不存在的,因為從執行緒的ThreadLocal裡沒有取出ConnectionHolder物件。那就新開一個事務唄,不過首先要看一下如何掛起一個事務。

掛起事務,如下圖:

 

 

分三種情況:


1、事務同步是活動的,即物理事務已經被系結到執行緒:

1.1、doSuspendSynchronization()方法傳回的List是空的(多資料源分散式事務時才不為空)

1.2、doSuspend(transaction)去掛起當前事務。

1.3、從ThreadLocal裡取出值,並同時清空ThreadLocal。

1.4、將這些值儲存在SuspendedResourcesHolder類中,表示這些都是被掛起的資源。

2、物理事務是活動的,但是還沒有系結到執行緒,此時只需掛起事務就行了。

3、沒有物理事務,什麼都不做,傳回null即可。


具體的掛起事務操作,請看下圖:

 

包括兩步:

1、將(邏輯)事務物件中關聯的物理事務ConnectionHolder清空。

2、在執行緒的ThreadLocal裡的Map中刪除這對DataSource->ConnectionHolder的對映。同時將ConnectionHolder傳回。

 

這個被傳回的ConnectionHolder(物理事務),就是上一張圖片中被掛起的資源suspendedResources。

下麵是開啟一個新的物理事務,如圖:

 

 

主要包括以下內容:

如果邏輯事務沒有和一個物理資源相關聯,或這個物理資源還沒有和一個事務同步,此時新建一個資料庫連結,並把這個連結包裝到一個ConnectionHolder裡。並設定給事務物件。

接下來將這個物理資源ConnectionHolder標記為已同步一個事務。然後將資料庫連結設定為非自動提交。最後把DataSource和ConnectionHolder系結到當前執行緒。

 

一個事務獲取後,傳回的結果是一個事務狀態,如下圖:

 

 

很重要的三點:


一是它包含了邏輯事務物件(已關聯了物理事務)。

二是需要表明這個事務是一個新開啟的物理事務,還是參與到已有的物理事務。

三是它包含了被掛起的(上一個)物理事務物件(如果有的話)。

 

下麵請看事務提交方法:

 

 

主要包括四部分:

1、如果事務已完成則丟擲異常。

2、主動回滾。

3、被動回滾,因為全域性範圍被設定了回滾。

4、進入提交事務。

 

下麵是提交邏輯:

 

 

主要關註兩點:

1、如果建立了儲存點,就將其釋放掉,因為現在已經在提交流程中,儲存點已經沒有用了。

2、只有當前的邏輯事務新開啟了物理事務時才提交。只是參與到已存在的物理事務中時不提交(因為這個物理事務還對應了其它沒有執行完的邏輯事務)。

 

下麵是具體的事務提交:

 

 

從事務狀態中獲取儲存的邏輯事務物件,再獲取它關聯的物理事務,再獲取關聯的資料庫連結,最後執行commit操作。

下麵請看回滾方法:

 

 

如果事務已完成則丟擲異常,否則執行回滾。

下麵是回滾邏輯:

 

1、如果有儲存點的回滾到儲存點。

2、如果是新開的物理事務,則進行回滾。

3、若是參與到已有的事務中,只能標記為回滾。


具體的執行回滾和標記回滾請看下圖:

 

 

無論是提交還是回滾,都表示一個事務的完成。如果它之前有掛起的事務,則需要進行恢復。如下圖:

 

 

下麵時事務恢復邏輯:

 

 

其中被掛起的資源就是ConnectionHolder物件。

具體恢復操作如下圖:

 

 

把DataSource->ConnectionHolder重新系結到執行緒的ThreadLocal裡的Map中。

 

PS:能認真看到這裡的,都是有耐心和愛學習的人,你一定會有所收穫的。

    贊(0)

    分享創造快樂