1. 程式人生 > 程式設計 >SpringBoot事務管理

SpringBoot事務管理

img

前言

  好久沒有寫部落格了,最近終於能沉下心來閱讀底層原始碼了(主要是上班時間比較閒(●'◡'●))。本文部分內容涉及spring容器沒有細講,有興趣的同學可以去看我容器啟動分析的文章(寫的第一篇文章比較爛,有時間應該會重新整理下)。

springboot自動配置基本原理

  SpringBoot首先在啟動的時候會讀取專案下的"META-INF/spring.factories"路徑的自動配置類,過濾器,監聽器,ApplicationContextInitializer等。mybatis的自動裝配就引入了MybatisAutoConfiguration類。
  分析mybatis的自動裝配前需要來分析下@EnableAutoConfiguration註解,SpringBoot的啟動註解@SpringBootApplication就自帶該註解,該註解引入了兩個類分別是AutoConfigurationImportSelector,AutoConfigurationPackages.Registrar。

  1. AutoConfigurationImportSelector: 該類實現了DeferredImportSelector介面(繼承ImportSelector介面),所以在spring進行容器重新整理的時候會呼叫該類的selectImports方法將一些元件註冊進容器當中。 這個類的selectImports方法:
    1. 從專案中讀取"META-INF/spring-autoconfigure-metadata.properties"檔案的內容(自動裝配引入的類)。
    2. 從快取中獲取EnableAutoConfiguration類的類名(SpringBoot啟動時讀取的自動配置類);
    3. 移除配置的忽略類(@SpringBootApplication/@EnableAutoConfiguration的exclude屬性配置)
    4. 使用過濾器篩選自動配置類。SpringBoot預設註冊了OnClassCondition過濾器,在spring-autoconfigure-metadata.properties的配置檔案中需要配置ConditionalOnClass對應的value就是OnClassCondition類去判斷的類)該過濾器篩選掉ClassLoader中缺失所需類的自動裝配類。
  2. AutoConfigurationPackages.Registrar:將AutoConfigurationPackages類註冊入容器,目前還不清楚該類的作用。官方描述:Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner).
    SpringBoot讀取自動裝配類流程圖如下:
    img

事務相關的自動配置(JDBC)

  事務相關的自動配置需要關注的有三個類,第一個是@EnableTransactionManagementspringboot用於開啟事務的註解,其他兩個類是通過spring.factories讀取的配置類分別是TransactionAutoConfigurationDataSourceTransactionManagerAutoConfiguration這些類用於向spring容器中注入一些事務相關的關鍵類。(這裡分析的是JDBC方向的,JTA原理類似)

DataSourceTransactionManagerAutoConfiguration

img

  DataSourceTransactionManagerAutoConfiguration該類主要向容器注入PlatformTransactionManager類,這邊注入的是DataSourceTransactionManager(注意的是這個類是自動注入最低優先順序的,應該是考慮到讓DataSource等先註冊)

img

TransactionAutoConfiguration

  TransactionAutoConfiguration該類主要向容器注入TransactionTemplate和支援spring的事務管理只要容器中有PlatformTransactionManager類就會自動開啟事務。所以很多教程springboot開啟事務管理需要新增@EnableTransactionManagement註解,其實完全是多餘的!只要你在容器中注入有spring的事務管理器就ok了。(springboot的jdbc-starter和jta-starter都幫我們注入了事務管理器)

EnableTransactionManagement

  該類就使用@Import向容器注入了TransactionManagementConfigurationSelector類,該類實現了ImportSelector 介面,向容器注入了AutoProxyRegistrar和ProxyTransactionManagementConfiguration類(預設的Proxy)。重點要看的是ProxyTransactionManagementConfiguration類。

ProxyTransactionManagementConfiguration

  ProxyTransactionManagementConfiguration實現了AbstractTransactionManagementConfiguration類。該類有個setConfigurers方法,該方法會將TransactionManagementConfigurer的實現類返回的PlatformTransactionManager返回賦值給TransactionInterceptor用於多事務管理器的預設管理。
  ProxyTransactionManagementConfiguration像spring容器添加了三個了:

  1. TransactionalEventListenerFactory,事務的監聽器,這個不管。
  2. AnnotationTransactionAttributeSource,該類用於解析類或方法上的事務註解。
  3. TransactionInterceptor,該類是事務起作用的核心類,當執行事務切面的時候就是執行該類的invoke方法
  4. BeanFactoryTransactionAttributeSourceAdvisor,該類在aop生成代理類的時候會作為管理類被加入到aop的攔截器鏈用於觸發回撥,執行事務操作。該類的TransactionAttributeSourcePointcut屬性在建立代理物件的時候呼叫match方法掃描該類及其所有方法,來確定該類是否需要被該攔截器處理。(aop的執行可以看我另外一篇部落格)。

事務原理分析

  主要看TransactionInterceptor的invoke方法,前面也說到當執行到代理物件的方法時會獲取該方法的所有攔截器來進行處理。事務新增的就是TransactionInterceptor攔截器。
  invokeWithinTransaction方法: 下圖可以很明顯的看出四步

  1. 開啟事務: createTransactionIfNecessary
  2. 執行方法:processedWithInvocation
  3. 如果出錯回滾:completeTransactionAfterThrowing
  4. 提交事務:commitTransactionAfterReturning
createTransactionIfNecessary

   關鍵PlatformTransactionManager.getTransaction();本質操作就是獲取資料庫連線,jdbc環境使用的就是DataSourceTransactionManager。簡單講一下就是根據事務的傳播機制獲取連線,然後根據readOnly、隔離級別等給連線設定屬性,還有對事務狀態等控制。

processedWithInvocation

   執行該類的方法,通過java8的FunctionalInterface將方法傳入呼叫。

completeTransactionAfterThrowing

   DataSourceTransactionManager的doRollback,最終呼叫的就是connection.rollback()(如果異常是Error||RuntimeException)。其他還對事務的狀態進行校驗,獲取savepoint,判斷是否為巢狀事務等。

commitTransactionAfterReturning

   DataSourceTransactionManager的doCommit,最終呼叫的就是connection.commit(),如果是巢狀繼承的事務則不會提交。

img

附錄:

事務併發引發問題

髒讀

    事務1 update data 1 > 2
    事務2 read data 2
    事務1 rollback data 2 > 1
    事務1 commit
    事務1 return data 2
複製程式碼

丟失修改

    事務1 read data 1
    事務2 read data 1
    事務1 update data 1 + 1 > 2
    事務2 update data 1 + 1 > 2
    事務1 commit
    事務2 commit
複製程式碼

不可重複讀

    事務1 read data 1
    事務2 updata data 1 > 2
    事務2 commit
    事務1 read data 2
複製程式碼

幻讀

   事務1 read data num 2
   事務2 insert delete num 2 > 1
   事務2 commit
   事務1 commit
   事務1 return data num 2
複製程式碼

事務隔離級別

read uncommitted

一個事務可以讀取另一個事務未提交事務的資料。
讀未提交會引起髒讀,不可重複讀、幻讀、丟失修改
複製程式碼

read committed (orcale預設)

一個事務可以讀取到另一個事務提交的資料。
讀已提交解決了髒讀,但還是會引起不可重複讀、幻讀、丟失修改。
複製程式碼

repeatable read (mysql預設)

可重複讀。一個事務對同一份資料讀取到的相同,不在乎其他事務對資料的修改。
解決不可重複讀。(實現機制是在事務A讀取之後將這些資料新增行級鎖,其他事務無法修改:待驗證)
會產生幻讀,丟失修改。
丟失修改可通過資料庫互斥鎖(悲觀鎖)來解決,
或者在欄位中加入版本號(樂觀鎖),也可以程式碼中自行加鎖。如果這個級別自動在查詢後新增行級鎖,那麼其他修改無法執行,就能解決丟失修改問題。
複製程式碼

serializable

嚴格的資料隔離,要求事務序列化執行,事務只能一個接一個執行。
嚴重影響併發能力。
解決所有併發事務引起問題。
複製程式碼

spring事務管理

隔離級別

TransactionDefinition介面中定義了五個表示隔離級別的常量:

  1. TransactionDefinition.ISOLATION_DEFAULT: 使用後端資料庫預設的隔離級別,Mysql 預設採用的 REPEATABLE_READ隔離級別 Oracle 預設採用的 READ_COMMITTED隔離級別.
  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
  3. TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生 TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
  4. TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生幹擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。
事務傳播機制

TransactionDefinition介面中定義了五個表示隔離級別的常量:

支援當前事務的情況:

  1. TransactionDefinition.PROPAGATION_REQUIRED:
    如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。
  2. TransactionDefinition.PROPAGATION_SUPPORTS:
    如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
  3. TransactionDefinition.PROPAGATION_MANDATORY:
    如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。

不支援當前事務的情況:

  1. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
    建立一個新的事務,如果當前存在事務,則把當前事務掛起。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
    以非事務方式執行,如果當前存在事務,則把當前事務掛起。
  3. TransactionDefinition.PROPAGATION_NEVER:
    以非事務方式執行,如果當前存在事務,則丟擲異常。

其他情況:

  1. TransactionDefinition.PROPAGATION_NESTED:
    如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

  這裡需要指出的是,前面的六種事務傳播行為是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 啟動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,巢狀的子事務不能單獨提交。如果熟悉 JDBC 中的儲存點(SavePoint)的概念,那巢狀事務就很容易理解了,其實巢狀的子事務就是儲存點的一個應用,一個事務中可以包括多個儲存點,每一個巢狀子事務。另外,外部事務的回滾也會導致巢狀子事務的回滾。

事務超時屬性

  所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

事務只讀屬性

  事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如資料來源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌為只讀的,以提高事務處理的效能。在 TransactionDefinition 中以 boolean 型別來表示該事務是否只讀。

回滾規則

  這些規則定義了哪些異常會導致事務回滾而哪些不會。預設情況下,事務只有遇到執行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)。 但是你可以宣告事務在遇到特定的檢查型異常時像遇到執行期異常那樣回滾。同樣,你還可以宣告事務遇到特定的異常不回滾,即使這些異常是執行期異常。

總結

  Spring通過良好的api將事務管理和資料來源封裝了起來,讓我們可以很輕鬆的使用不同環境中的事務,但我覺得Spring事務管理這塊擴充套件性還是略微有些差的,例如我原先想將事務支援多執行緒同一事務,原以為只要將每個執行緒的儲存的連線統一即可,但隨後發現必須要重寫攔截器才能實現,Spring攔截器不對外修改事務狀態以及一些處理的覆蓋。