Spring事務管理器詳解
理解Spring事務抽象
Spring事務抽象的關鍵是事務策略的概念。事務策略是由TransactionManager定義的,特別是用于強制事務管理的
org.springframework.transaction.PlatformTransactionManager接口和用于響應式事務管理的
org.springframework.transaction.ReactiveTransactionManager接口。下面的清單顯示了
PlatformTransactionManager API的定義:?
這主要是一個服務提供者接口(SPI),盡管你可以從應用程序代碼中以編程方式使用它。因為
PlatformTransactionManager是一個接口,所以可以根據(jù)需要輕松模擬或存根。它沒有綁定到查找策略,比如JNDI。PlatformTransactionManager實現(xiàn)的定義類似于Spring Framework IoC容器中的任何其他對象(或bean)。
同樣,為了與Spring的理念保持一致,可以由PlatformTransactionManager接口的任何方法拋出的TransactionException是未檢查的(也就是說,它擴展了java.lang.
exception.RuntimeException類)。事務基礎結構失敗幾乎總是致命的。在極少數(shù)情況下,應用程序代碼實際上可以從事務失敗中恢復,應用程序開發(fā)人員仍然可以選擇捕獲和處理TransactionException。不過對于開發(fā)人員并沒有強制這樣做。
getTransaction(TransactionDefinition)方法返回一個TransactionStatus對象,這取決于TransactionDefinition參數(shù)。如果當前調用堆棧中存在匹配的事務,則返回的TransactionStatus可以表示一個新的事務,也可以表示一個現(xiàn)有的事務。后一種情況的含義是,與Java EE事務上下文一樣,TransactionStatus與執(zhí)行線程相關聯(lián)。
從Spring Framework 5.2開始,Spring還為使用響應式類型或Kotlin協(xié)程的響應式應用程序提供了事務管理抽象。下面的清單顯示了
org.springframework.transaction.ReactiveTransactionManager定義的事務策略:?
TransactionDefinition接口具體說明:
Propagation(傳播特性):通常,事務范圍內的所有代碼都在該事務中運行。但是,如果在事務上下文已經存在時運行事務方法,則可以指定該行為。例如,代碼可以繼續(xù)在現(xiàn)有事務中運行(常見情況),也可以暫?,F(xiàn)有事務并創(chuàng)建新事務。
Isolation(隔離性):該事務與其他事務的工作隔離的程度。例如,這個事務能看到其他事務未提交的寫嗎?
Timeout(超時時間):該事務在超時和被底層事務基礎結構自動回滾之前運行的時間。
Read-Only(只讀狀態(tài)):當代碼讀取但不修改數(shù)據(jù)時,可以使用只讀事務。在某些情況下,例如使用Hibernate時,只讀事務可能是一種有用的優(yōu)化。
這些設置反映了標準的事務概念。如有必要,請參考討論事務隔離級別和其他核心事務概念的資源。理解這些概念對于使用Spring框架或任何事務管理解決方案都是至關重要的。
TransactionStatus接口為事務代碼提供了一種控制事務執(zhí)行和查詢事務狀態(tài)的簡單方法。這些概念應該很熟悉,因為它們對所有事務api都很常見。下面的清單顯示了TransactionStatus接口:?
無論在Spring中選擇聲明式事務管理還是編程式事務管理,定義正確的TransactionManager實現(xiàn)都是絕對必要的。通常通過依賴注入來定義此實現(xiàn)。
TransactionManager實現(xiàn)通常需要了解它們工作的環(huán)境:JDBC、JTA、Hibernate等等。下面的示例展示了如何定義本地
PlatformTransactionManager實現(xiàn)(在本例中,使用普通JDBC)。
首先,你的先定義一個數(shù)據(jù)源:
Java Config:?
相關的
PlatformTransactionManager bean定義隨后具有對DataSource定義的引用。它應該類似于下面的例子:
Java Config:?
Hibernate事務配置
還可以輕松地使用Hibernate本地事務,如以下示例所示。在這種情況下,需要定義一個Hibernate LocalSessionFactoryBean,應用程序代碼可以使用它來獲取Hibernate會話實例。
DataSource bean定義類似于前面顯示的本地JDBC示例
本例中的txManager bean屬于HibernateTransactionManager類型。就像DataSourceTransactionManager需要對DataSource的引用一樣,HibernateTransactionManager也需要對SessionFactory的引用。下面的例子聲明了sessionFactory和txManager bean:
LocalSessionFactoryBean是個FactoryBean,同時實現(xiàn)了InitializingBean接口,所以在當前類初始化的時候,會調用afterPropertiesSet方法,該方法中會初始化SessionFactory對象。
將資源與事務同步
現(xiàn)在應該清楚了如何創(chuàng)建不同的事務管理器,以及如何將它們鏈接到需要同步到事務的相關資源(例如,DataSourceTransactionManager到JDBC DataSource,Hibernate TransactionManager到Hibernate SessionFactory,等等)。本節(jié)描述了應用程序代碼(直接或間接地,通過使用JDBC、Hibernate或JPA等持久性API)如何確保正確創(chuàng)建、重用和清理這些資源。本節(jié)還討論了如何(可選地)通過相關的TransactionManager觸發(fā)事務同步。
- 高級同步方法
首選方法是使用Spring的最高級別基于模板的持久性集成API,或者使用具有事務感知工廠bean或代理的本地ORM API來管理本地資源工廠。這些事務感知解決方案在內部處理資源的創(chuàng)建和重用、清理、資源的可選事務同步以及異常映射。因此,用戶數(shù)據(jù)訪問代碼不必處理這些任務。通常,使用本機ORM API,或者通過使用JdbcTemplate采用模板方法進行JDBC訪問。
- 低級同步方法
DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils(適用于JPA)、SessionFactoryUtil(適用于Hibernate)等類存在于較低級別。當您希望應用程序代碼直接處理本機持久性API的資源類型時,可以使用這些類來確保獲得正確的Spring Framework托管實例,同步事務(可選),并將過程中發(fā)生的異常正確映射到一致的API。
例如,在JDBC的情況下,你可以使用Spring的org.springframework.jdbc.datasource.DataSourceUtils類,而不是在數(shù)據(jù)源上直接調用getConnection()方法,如下所示:?
如果現(xiàn)有事務已經有一個同步(鏈接)到它的連接,則返回該實例。否則,方法調用將觸發(fā)新連接的創(chuàng)建,該連接(可選地)同步到任何現(xiàn)有事務,并可用于同一事務中的后續(xù)重用。如前所述,任何SQLException都被包裝在Spring框架
CannotGetJdbcConnectionException中,這是Spring框架中未檢查的DataAccessException類型的層次結構之一。這種方法提供了比從SQLException輕松獲得的更多信息,并確保了跨數(shù)據(jù)庫、甚至跨不同持久性技術的可移植性。
這種方法在沒有Spring事務管理的情況下也可以工作(事務同步是可選的),因此無論是否使用Spring進行事務管理,都可以使用它。
當然,一旦你使用了Spring的JDBC支持、JPA支持或Hibernate支持,你通常不喜歡使用DataSourceUtils或其他輔助類,因為你更喜歡通過Spring抽象工作而不是直接使用相關的api。例如,如果使用Spring JdbcTemplate或jdbc。對象包來簡化你的JDBC使用,正確的連接檢索發(fā)生在幕后,你不需要編寫任何特殊的代碼。
- TransactionAwareDataSourceProxy
在最底層存在
TransactionAwareDataSourceProxy類。這是目標DataSource的代理,它封裝目標DataSource以添加對Spring托管事務的感知。
除非必須調用現(xiàn)有代碼并傳遞標準JDBC DataSource接口實現(xiàn),否則幾乎你不需要或不想使用這個類。在這種情況下,這段代碼可能是可用的,但參與了spring管理的事務。你可以使用前面提到的高級抽象來編寫新代碼。
聲明式事務管理
Spring Framework的聲明式事務管理是通過Spring面向方面編程(AOP)實現(xiàn)的。然而,由于事務方面的代碼是隨Spring Framework一起提供的,并且可以以樣板的方式使用,因此通常不需要理解AOP概念就可以有效地使用這些代碼。
僅僅告訴你使用@Transactional注解注釋的類,將@
EnableTransactionManagement添加到你的配置中,并期望你理解它是如何工作的,這是不夠的。為了加深理解,本節(jié)將在與事務相關的問題上下文中解釋Spring框架的聲明式事務基礎結構的內部工作原理。
關于Spring Framework的聲明性事務支持,需要掌握的最重要的概念是,這種支持是通過AOP代理啟用的,并且事務Advice是由元數(shù)據(jù)驅動的(目前是基于XML或注釋的)。AOP與事務元數(shù)據(jù)的組合產生了一個AOP代理,該代理使用TransactionInterceptor和適當?shù)腡ransactionManager實現(xiàn)來圍繞方法調用驅動事務。
Spring Framework的TransactionInterceptor為命令式和反應式編程模型提供事務管理。攔截器通過檢查方法返回類型來檢測所需的事務管理風格。返回響應式類型(如Publisher或Kotlin Flow(或其子類型))的方法符合響應式事務管理的條件。包括void在內的所有其他返回類型都使用代碼路徑進行強制事務管理。
事務管理會影響所需的事務管理器。強制事務需要PlatformTransactionManager,而響應事務使用ReactiveTransactionManager實現(xiàn)。
@Transactional通常使用
PlatformTransactionManager管理的線程綁定事務,將事務暴露給當前執(zhí)行線程中的所有數(shù)據(jù)訪問操作。注意:這不會傳播到方法中新啟動的線程。由
ReactiveTransactionManager管理的反應事務使用Reactor上下文而不是線程本地屬性。因此,所有參與的數(shù)據(jù)訪問操作都需要在同一反應管道中的同一Reactor上下文中執(zhí)行。
下圖顯示了在事務代理上調用方法的概念視圖:
- 聲明性事務實現(xiàn)示例
考慮以下接口及其附屬實現(xiàn)。本例使用Foo和Bar類作為占位符,這樣你就可以專注于事務的使用,而不必關注特定的域模型。就本例而言,DefaultFooService類在每個實現(xiàn)方法的主體中拋出
UnsupportedOperationException實例是好的。該行為允許你查看正在創(chuàng)建的事務,然后回滾以響應UnsupportedOperationException實例。FooService接口如下所示:?
接口實現(xiàn):?
以FooService接口的前兩個方法getFoo(String)和getFoo為例配置事務攔截。?
<aop:config/>定義確保txAdvice bean定義的事務建議在程序中的適當點運行。首先,定義一個切入點,該切入點與FooService接口(fooServiceOperation)中定義的任何操作的執(zhí)行相匹配。然后使用advisor將切入點與txAdvice關聯(lián)起來。結果表明,在執(zhí)行fooServiceOperation時,運行由txAdvice定義的通知。
一個常見的要求是使整個服務層具有事務性。做到這一點的最佳方法是更改切入點表達式以匹配服務層中的任何操作。以下示例顯示了如何執(zhí)行此操作:?
- 回滾聲明性事務
上面介紹了如何在應用程序中以聲明方式為類(通常是服務層類)指定事務設置的基礎知識。本節(jié)描述如何在XML配置中以簡單的聲明式方式控制事務的回滾。
向Spring框架的事務基礎設施指示事務的工作要回滾的推薦方法是從當前在事務上下文中執(zhí)行的代碼拋出異常。Spring框架的事務基礎結構代碼在調用堆棧中彈出氣泡時捕獲任何未處理的異常,并確定是否將事務標記為回滾。
在默認配置中,Spring框架的事務基礎結構代碼僅在運行時未檢查異常的情況下將事務標記為回滾。也就是說,當拋出的異常是RuntimeException的實例或子類時。(默認情況下,錯誤實例也會導致回滾)。從事務方法拋出的已檢查異常不會導致默認配置中的回滾。
你可以通過指定回滾規(guī)則,準確地配置哪些Exception類型標記要回滾的事務,包括已檢查的異常。
回滾規(guī)則
回滾規(guī)則確定在拋出給定異常時是否應該回滾事務,這些規(guī)則基于模式。模式可以是完全限定類名,也可以是異常類型(必須是Throwable的子類)的完全限定類名的子字符串,目前不支持通配符。例如,"
javax.servlet.ServletException"或"ServletException"將匹配javax.servlet.ServletException及其子類。可以通過Rollback-for和no-rollback-for屬性在XML中配置回滾規(guī)則,這些屬性允許將模式指定為字符串。當使用@Transactional時,可以通過rollbackFor/noRollbackFor和
rollbackForClassName/noRollbackForClassName屬性來配置回滾規(guī)則,這些屬性允許分別將模式指定為類引用或字符串。當異常類型被指定為類引用時,其全限定名將用作模式。因此,@Transactional(rollbackFor = example.CustomException.class)等價于@Transactional(rollbackForClassName = "example.CustomException")。
以下XML片段演示了如何通過回滾for屬性提供異常模式,為已檢查的、特定于應用程序的異常類型配置回滾:?
如果不希望在引發(fā)異常時回滾事務,也可以指定“無回滾”規(guī)則。下面的示例告訴Spring Framework的事務基礎結構即使面對未處理的
InstrumentNotFoundException也要提交附帶事務:?
當Spring Framework的事務基礎設施捕捉到異常并參考配置的回滾規(guī)則以確定是否將事務標記為回滾時,最強的匹配規(guī)則獲勝。因此,在以下配置的情況下,
InstrumentNotFoundException以外的任何異常都會導致伴隨事務的回滾:?
你還可以通過編程方式指示所需的回滾。盡管這個過程很簡單,但它具有很強的侵入性,并將您的代碼緊密地耦合到Spring Framework的事務基礎設施。以下示例顯示了如何以編程方式指示所需的回滾:?