Spring事務管理:非常規指南 - marcobehler
您可以使用本指南對Spring的事務管理(包括@Transactional批註)的工作方式進行深入的實際瞭解。
唯一的前提條件?您需要對ACID有一個大概的瞭解,即什麼是資料庫事務以及為什麼要使用它們。此外,儘管仍然適用於Spring的一般原則,但這裡也不介紹XATransactions或ReactiveTransactions。
與Spring官方文件相反,本指南不會將您與諸如物理或邏輯事務之類的術語混淆。
相反,您將以非常規的方式學習Spring事務管理:從頭開始,逐步進行。
開始,提交和回滾事務
無論您使用的是Spring的@Transactional批註,純Hibernate,jOOQ還是任何其他資料庫庫,都沒有關係。最後,它們都在開啟和關閉資料庫事務中做同樣的事情,就是:
Connection connection = dataSource.getConnection(); // (1) try (connection) { connection.setAutoCommit(false); // (2) // execute some SQL statements... connection.commit(); // (3) } catch (SQLException e) { connection.rollback(); // (4) } |
- 您顯然需要與資料庫的連線。儘管在大多數企業級應用程式中,您將配置一個DataSource並從中獲取連線,但DriverManager.getConnection(...)也可以正常工作。
- 這是在Java中啟動資料庫事務的唯一方法,即使該名稱聽起來有些奇怪。 AutoCommit(true)在其自己的事務中包裝每個SQL語句,而AutoCommit(false)相反:您是事務的主控者。
- 讓我們進行交易...
- 或回滾我們的更改(如果有例外)。
是的,只要您使用@Transactional批註,這4行就簡化了,Spring幫你做了這一切。在下一章中,您將瞭解其工作原理。HikariCP之類的連線池庫可能會根據配置自動為您切換自動提交模式。但這是另一個主題。
儲存點和隔離級別
如果您已經使用過Spring的@Transactional批註,那麼您可能會遇到以下情況:
@Transactional(propagation=TransactionDefinition.NESTED, isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED) |
稍後我們將介紹巢狀的Spring事務和隔離級別,但是再次幫助您瞭解這些引數到底歸結為以下JDBC程式碼:
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1) Savepoint savePoint = connection.setSavepoint(); // (2) ... connection.rollback(savePoint); |
- 這就是Spring如何在資料庫連線上設定隔離級別。不完全是火箭科學,是嗎?
- Spring中的巢狀事務實際上只是JDBC儲存點。如果您不知道儲存點是什麼,請檢視本教程。請注意,儲存點支援取決於您的JDBC驅動程式/資料庫。
Spring中的事務管理如何工作
現在您有了一個良好的JDBC事務基礎,讓我們看一下Spring。
舊版事務管理:XML
過去,當XML配置成為Spring專案的規範時,您還可以直接在XML中配置事務。除了幾個遺留的企業專案,您將再也找不到這種方法了,因為它已經被更簡單的@Transactional註釋所取代。
因此,我們將在本指南中跳過XML配置,但是可以快速瀏覽一下它的外觀(直接取自Spring官方文件):
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> |
Spring使用AOP(面向方面的程式設計)來進行事務處理。您可以在Spring官方文件中瞭解有關AOP的更多資訊。
Spring的@Transactional註釋
現在讓我們看一下現代Spring事務管理通常是什麼樣的:
public class UserService { @Transactional public void registerUser(User user) { //...validate the user userDao.save(user); } } |
只要您在Spring配置上設定了@EnableTransactionManagement批註(以及配置了其他兩個Bean-稍後將進行更多說明),就可以使用@Transactional批註對方法進行批註,並確保您的方法在資料庫事務內執行。
“在資料庫事務內部執行”的真正含義是什麼?有了上一部分的知識,上面的程式碼直接轉換(簡化)為:
public class UserService { public void registerUser(User user) { Connection connection = dataSource.getConnection(); // (1) try (connection) { connection.setAutoCommit(false); // (1) //...validate the user userDao.save(user); // (2) connection.commit(); // (1) } catch (SQLException e) { connection.rollback(); // (1) } } } |
- 這僅僅是JDBC連線的標準開啟和關閉。請參閱上一節。這就是Spring的事務註釋自動為您完成的,而無需您編寫它。
- 這是您自己的程式碼,可以通過DAO儲存使用者。
這個示例可能看起來有些簡化,但是讓我們看一下Spring如何神奇地為您插入此連線/事務程式碼。
代理
Spring不能像我上面那樣真正重寫您的Java類來插入連線程式碼。您的registerUser()方法實際上只是呼叫userDao.save(user),無法即時更改它。
但是Spring有一個優勢。它的核心是一個IoC容器。它為您例項化一個UserService,並確保將該UserService自動連線到需要UserService的任何其他bean中。
現在,每當在bean上使用@Transactional時,Spring都會使用一個小技巧。它不僅例項化UserService,而且例項化該UserService的事務代理。讓我們在圖片中看到它。
從該圖中可以看到,代理只有一項工作。
- 開啟和關閉資料庫連線/事務。
- 然後委託給真正的UserService。
- 而其他的bean,例如UserRestController,將永遠不會知道它們正在與代理對話,而不是真實的對話。
快速考試 看看下面的原始碼,並告訴我Spring會自動構造什麼樣的UserService,假設它已標有@Transactional或具有@Transactional方法。
@Configuration @EnableTransactionManagement public static class MyAppConfig { @Bean public UserService userService() { // (1) return new UserService(); } } |
Spring在這裡構造了UserService類的動態代理,可以為您開啟和關閉資料庫事務。在Cglib庫的幫助下通過子類代理。還有其他構造代理的方法,但現在暫時不做介紹。
PlatformTransactionManager
現在僅缺少一條關鍵資訊。您的UserService會即時進行代理,並且代理會開啟併為您關閉連線/事務。
但這意味著,代理需要一個資料來源:要獲得連線,提交,回滾,關閉連線等。在Spring中,有一個花哨的名稱表示處理所有事務狀態的介面,稱為PlatformTransactionManager。
有許多不同的實現,但是最簡單的實現是DataSourceTransactionManager,並且憑藉從簡單的Java章節中學到的知識,您將確切知道它的作用。首先,讓我們看一下Spring配置:
@Bean public DataSource dataSource() { return null; // (1) } @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); // (2) } |
- 在此處返回null顯然沒有意義,但為簡潔起見放在此處。您可建立MySQL,Postgres,Oracle或連線池資料來源。
- 您在此處建立一個新的TxManager,它將獲取您的DataSource。
因此,讓我們從上面擴充套件圖片:
總結一下:
- 如果Spring在bean上檢測到@Transactional,它將建立該bean的動態代理。
- 代理有權訪問TransactionManager,並要求其開啟和關閉事務/連線。
- TransactionManager本身將簡單地執行您在普通Java部分中所做的事情:“操縱” JDBC連線。
@Transactional深度挖掘
現在,有兩個有趣的用例,涉及到Spring的事務性註釋。
讓我們看一下“物理”與“邏輯”事務。
想象以下兩個事務類。
@Service public class UserService { @Autowired private InvoiceService invoiceService; @Transactional public void invoice() { invoiceService.createPdf(); // send invoice as email, etc. } } @Service public class InvoiceService { @Transactional public void createPdf() { // ... } } |
UserService具有事務性invoice()方法。它將呼叫另一個事務方法InvoiceService上的createPdf()。
現在就資料庫事務而言,這實際上應該僅僅是一個資料庫事務。(請記住:getConnection().setAutocommit(false).commit())Spring呼叫了此物理事務,即使這聽起來有些混亂。
從Spring的角度來看,事務有兩個邏輯部分:第一個在UserService中,另一個在InvoiceService中。Spring必須足夠聰明,才能知道兩個@Transactional方法都應使用相同的基礎資料庫事務。
在InvoiceService進行以下更改之後,情況會有什麼不同?
@Service public class InvoiceService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void createPdf() { // ... } } |
這意味著您的程式碼將開啟與資料庫的兩個(物理)連線/事務。Spring現在足夠聰明,兩個邏輯事務塊(invoice()/ createPdf())現在也對映到兩個不同的資料庫事務。
傳播方式
檢視Spring原始碼時,您會發現各種傳播模式,可以將它們插入@Transactional方法中。
- REQUIRED(0)
- SUPPORTS(1)
- MANDATORY(2)
- REQUIRES_NEW(3)
- NOT_SUPPORTED(4)
- NEVER(5)
- NESTED(6)
解釋:
- REQUIRED(預設):我的方法需要一個事務,要麼為我開啟一個事務,要麼使用現有的事務:getConnection().setAutocommit(false)commit()。
- SUPPORTS:我並不真正在乎事務是否開啟,我可以以任何一種方式工作:與JDBC無關
- MANDATORY強制性的:我不會自己開啟一個事務,但是如果沒有人開啟一個事務,我會哭泣,與JDBC無關
- Require_new:我要我完全擁有的事務: getConnection().setAutocommit(false)commit()。
- Not_Supported:我真的不喜歡事務,我會嘗試掛起當前正在執行的事務,與JDBC無關
- NEVER:如果其他人啟動了事務,我會哭泣→與JDBC無關
- NESTED:聽起來很複雜,但實際上我們只是在談論儲存點!: connection.setSavepoint()
如您所見,大多數傳播模式實際上與資料庫或JDBC無關,而與您如何使用Spring構建程式的方式以及期望交易的方式/時間/位置有關。
隔離等級
當您像這樣配置@Transactional批註時會發生什麼:
@Transactional(isolation = Isolation.REPEATABLE_READ)
導致以下結果:
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
您可以在此處閱讀有關隔離級別的更多資訊,但建議您在使用隔離級別甚至在事務中切換隔離級別時,必須確保諮詢JDBC驅動程式/資料庫,以瞭解受支援的內容和不支援的內容。
最常見的@Transactional陷阱
Spring初學者通常會遇到一個陷阱。看下面的程式碼:
@Service public class UserService { @Transactional public void invoice() { createPdf(); // send invoice as email, etc. } @Transactional(propagation = Propagation.REQUIRES_NEW) public void createPdf() { // ... } } |
您有一個帶有事務invoice()方法的UserService類。呼叫createPDF(),這也是事務性的。
一旦有人呼叫invoice(),您覺得會開啟幾各實際事務?
答案不是兩個,而是一個。為什麼?
讓我們回到本指南的代理部分。Spring為您注入了該事務代理,但是一旦您進入UserService類並呼叫其他內部方法,就不再涉及代理:沒有新事務可供您使用。
讓我們看一下圖片:
有一些技巧(例如self-injection),您可以用來解決此限制。但是主要的收穫是:始終牢記代理事務邊界。
Spring + Hibernate事務管理如何工作
在某些時候,您將希望您的Spring應用程式與另一個資料庫庫整合,例如Hibernate,Jooq等。讓我們以Hibernate為例。
假設您有一個@Transactional Spring方法,並將其與DataSourcePlatformTransactionManager一起使用,就像上一節中討論的那樣。因此,Spring將為您開啟和關閉該DataSource上的連線。
但是,如果您的程式碼呼叫了Hibernate,則Hibernate本身將最終呼叫其自己的SessionFactory來建立和關閉新會話(〜=連線)並在沒有 Spring 的情況下管理它們的狀態。因此,Hibernate不會知道任何現有的Spring事務。
有一個簡單的解決方案(針對終端使用者):您將在HibernateTransactionManager或JpaTransactionManager中使用,而不是在Spring配置中使用DataSourcePlatformTransactionManager。
專門的HibernateTransactionManager將確保:
- 通過Hibernate(即SessionFactory)開啟/關閉連線/事務
- 足夠聰明,可以讓您在非休眠狀態(即純JDBC程式碼)中使用相同的連線/事務
與往常一樣,圖片可能更容易理解(不過請注意,代理和真實服務之間的流在概念上只是正確的,而且過於簡化)。
簡而言之,就是如何整合Spring和Hibernate。對於其他整合或更深入的瞭解,有助於快速檢視Spring提供的所有可能的PlatformTransactionManager實現。
相關文章
- Spring 事務管理Spring
- Spring事務管理Spring
- Spring的事務管理(二)宣告式事務管理Spring
- Spring系列.事務管理Spring
- Spring系列-事務管理Spring
- Spring的事務管理Spring
- Spring事務管理總結Spring
- Spring 中的事務管理Spring
- spring 事務管理機制Spring
- Spring事務管理全解析Spring
- Java版本安裝完整指南 - marcobehlerJava
- Spring的事務管理入門:程式設計式事務管理(TransactionTemplate)Spring程式設計
- spring宣告式事務管理配置Spring
- (四)Spring中的事務管理Spring
- spring datasource 配置及事務管理Spring
- Spring 程式設計式事務管理Spring程式設計
- 分散式鎖和spring事務管理分散式Spring
- Spring事務管理(詳解+例項)Spring
- Spring整合Hibernate的事務管理Spring
- 關於SPRING的事務管理_求助Spring
- Spring事務管理—aop:pointcut expression解析SpringExpress
- 使用Spring Boot實現事務管理Spring Boot
- 全面分析 Spring 的程式設計式事務管理及宣告式事務管理Spring程式設計
- spring事務管理原始碼分析(二)事務處理流程分析Spring原始碼
- Spring的事務管理(一) Spring事務管理的實現,事務的屬性(隔離級別,傳播行為,只讀)Spring
- Java版本功能差異一覽指南 - marcobehlerJava
- Spring框架中配置事務管理器Spring框架
- 菜鳥學SSH(六)——Spring事務管理Spring
- spring 事務Spring
- spring事務Spring
- spring事務管理的一些注意點Spring
- Spring boot +mybatis 實現宣告式事務管理Spring BootMyBatis
- 基於Spring中的事務管理機制Spring
- MyBatis5:MyBatis整合Spring事務管理(上篇)MyBatisSpring
- spring事務管理器設計思想(一)Spring
- spring事務管理器設計思想(二)Spring
- Spring中事務管理org.springframework.transactionSpringFramework
- 解析Spring Boot中的事務管理機制Spring Boot