Spring事務管理:非常規指南 - marcobehler

19-10-30 banq
                   

您可以使用本指南對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)
}

  1. 您顯然需要與數據庫的連接。盡管在大多數企業級應用程序中,您將配置一個DataSource并從中獲取連接,但DriverManager.getConnection(...)也可以正常工作。
  2. 這是在Java中啟動數據庫事務的唯一方法,即使該名稱聽起來有些奇怪。?AutoCommit(true)在其自己的事務中包裝每個SQL語句,而AutoCommit(false)相反:您是事務的主控者。
  3. 讓我們進行交易...
  4. 或回滾我們的更改(如果有例外)。

是的,只要您使用@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);

  1. 這就是Spring如何在數據庫連接上設置隔離級別。不完全是火箭科學,是嗎?
  2. 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)
? ? ? ? }
? ? }
}

  1. 這僅僅是JDBC連接的標準打開和關閉。請參閱上一節。這就是Spring的事務注釋自動為您完成的,而無需您編寫它。
  2. 這是您自己的代碼,可以通過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)
}

  1. 在此處返回null顯然沒有意義,但為簡潔起見放在此處。您可創建MySQL,Postgres,Oracle或連接池數據源。
  2. 您在此處創建一個新的TxManager,它將獲取您的DataSource。

因此,讓我們從上面擴展圖片:

總結一下:

  1. 如果Spring在bean上檢測到@Transactional,它將創建該bean的動態代理。
  2. 代理有權訪問TransactionManager,并要求其打開和關閉事務/連接。
  3. 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應用程序與另一個數據庫庫集成,例如HibernateJooq等。讓我們以Hibernate為例。

假設您有一個@Transactional Spring方法,并將其與DataSourcePlatformTransactionManager一起使用,就像上一節中討論的那樣。因此,Spring將為您打開和關閉該DataSource上的連接。

但是,如果您的代碼調用了Hibernate,則Hibernate本身將最終調用其自己的SessionFactory來創建和關閉新會話(?=連接)并在沒有?Spring?的情況下管理它們的狀態。因此,Hibernate不會知道任何現有的Spring事務。

有一個簡單的解決方案(針對最終用戶):您將在HibernateTransactionManagerJpaTransactionManager中使用,而不是在Spring配置中使用DataSourcePlatformTransactionManager

專門的HibernateTransactionManager將確保:

  1. 通過Hibernate(即SessionFactory)打開/關閉連接/事務
  2. 足夠聰明,可以讓您在非休眠狀態(即純JDBC代碼)中使用相同的連接/事務

與往常一樣,圖片可能更容易理解(不過請注意,代理和真實服務之間的流在概念上只是正確的,而且過于簡化)。

簡而言之,就是如何集成Spring和Hibernate。對于其他集成或更深入的了解,有助于快速查看Spring提供的所有可能的PlatformTransactionManager實現。

?

                   

美女漫画大全