理解 Spring Framework 事務抽象

Spring 事務抽象的關鍵在於事務策略的概念。事務策略由 TransactionManager 定義,具體而言,命令式事務管理使用 org.springframework.transaction.PlatformTransactionManager 介面,而響應式事務管理使用 org.springframework.transaction.ReactiveTransactionManager 介面。以下列表展示了 PlatformTransactionManager API 的定義

public interface PlatformTransactionManager extends TransactionManager {

	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

	void commit(TransactionStatus status) throws TransactionException;

	void rollback(TransactionStatus status) throws TransactionException;
}

這主要是一個服務提供者介面 (SPI),儘管你可以在應用程式碼中程式化地使用它。由於 PlatformTransactionManager 是一個介面,因此可以根據需要輕鬆地模擬或存根它。它不與查詢策略(例如 JNDI)繫結。PlatformTransactionManager 實現就像 Spring Framework IoC 容器中的任何其他物件(或 bean)一樣定義。僅憑這一優點就使得 Spring Framework 事務成為一個有價值的抽象,即使在使用 JTA 時也是如此。與直接使用 JTA 相比,你可以更容易地測試事務程式碼。

再次,與 Spring 的理念保持一致,PlatformTransactionManager 介面的任何方法可能丟擲的 TransactionException 都是非檢查異常(即,它繼承自 java.lang.RuntimeException 類)。事務基礎設施故障幾乎總是致命的。在應用程式碼確實可以從事務故障中恢復的極少數情況下,應用開發者仍然可以選擇捕獲並處理 TransactionException。關鍵點在於開發者不是被迫這樣做的。

getTransaction(..) 方法根據 TransactionDefinition 引數返回一個 TransactionStatus 物件。如果當前呼叫棧中存在匹配的事務,返回的 TransactionStatus 可能表示一個新事務,或者表示一個現有事務。後者的含義是,與 Jakarta EE 事務上下文一樣,TransactionStatus 與執行執行緒相關聯。

Spring 還為使用響應式型別或 Kotlin Coroutines 的響應式應用提供了事務管理抽象。以下列表展示了由 org.springframework.transaction.ReactiveTransactionManager 定義的事務策略

public interface ReactiveTransactionManager extends TransactionManager {

	Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;

	Mono<Void> commit(ReactiveTransaction status) throws TransactionException;

	Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

響應式事務管理器主要是一個服務提供者介面 (SPI),儘管你可以在應用程式碼中程式化地使用它。由於 ReactiveTransactionManager 是一個介面,因此可以根據需要輕鬆地模擬或存根它。

TransactionDefinition 介面指定了

  • 傳播 (Propagation):通常,事務範圍內的所有程式碼都在該事務中執行。但是,如果在一個事務上下文已存在時執行一個事務方法,你可以指定其行為。例如,程式碼可以繼續在現有事務中執行(常見情況),或者現有事務可以被掛起並建立新事務。Spring 提供了 EJB CMT 中熟悉的所有事務傳播選項。要了解 Spring 中事務傳播的語義,請參閱事務傳播

  • 隔離 (Isolation):此事務與其他事務的工作隔離的程度。例如,此事務能否看到其他事務未提交的寫入?

  • 超時 (Timeout):此事務在超時並被底層事務基礎設施自動回滾之前執行的時間長度。

  • 只讀狀態 (Read-only status):當你的程式碼只讀而不修改資料時,可以使用只讀事務。只讀事務在某些情況下可能是一個有用的最佳化,例如使用 Hibernate 時。

這些設定反映了標準的事務概念。如有必要,請查閱討論事務隔離級別和其他核心事務概念的資源。理解這些概念對於使用 Spring Framework 或任何事務管理解決方案至關重要。

TransactionStatus 介面為事務程式碼提供了一種簡單的方式來控制事務執行和查詢事務狀態。這些概念應該很熟悉,因為它們是所有事務 API 的共同點。以下列表展示了 TransactionStatus 介面

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

	@Override
	boolean isNewTransaction();

	boolean hasSavepoint();

	@Override
	void setRollbackOnly();

	@Override
	boolean isRollbackOnly();

	void flush();

	@Override
	boolean isCompleted();
}

無論你在 Spring 中選擇宣告式還是程式化事務管理,定義正確的 TransactionManager 實現都是絕對必要的。通常透過依賴注入來定義此實現。

TransactionManager 實現通常需要了解它們工作所在的環境:JDBC、JTA、Hibernate 等。以下示例展示瞭如何定義本地 PlatformTransactionManager 實現(在此示例中,使用普通 JDBC)。

你可以透過建立類似於以下的 bean 來定義 JDBC DataSource

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverClassName}" />
	<property name="url" value="${jdbc.url}" />
	<property name="username" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
</bean>

相關的 PlatformTransactionManager bean 定義然後引用 DataSource 定義。它應該類似於以下示例

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>

如果你在 Jakarta EE 容器中使用 JTA,那麼你將使用透過 JNDI 獲取的容器 DataSource,並結合 Spring 的 JtaTransactionManager。以下示例展示了 JTA 和 JNDI 查詢版本的外觀

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/jee
		https://www.springframework.org/schema/jee/spring-jee.xsd">

	<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

	<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

	<!-- other <bean/> definitions here -->

</beans>

JtaTransactionManager 不需要知道 DataSource(或任何其他特定資源),因為它使用容器的全域性事務管理基礎設施。

上述 dataSource bean 的定義使用了 jee 名稱空間中的 <jndi-lookup/> 標籤。有關更多資訊,請參見JEE Schema
如果你使用 JTA,無論你使用何種資料訪問技術(無論是 JDBC、Hibernate JPA 還是任何其他支援的技術),你的事務管理器定義都應該相同。這是因為 JTA 事務是全域性事務,可以登記任何事務資源。

在所有 Spring 事務設定中,應用程式碼無需更改。你只需更改配置即可更改事務的管理方式,即使這種更改意味著從本地事務遷移到全域性事務,反之亦然。

Hibernate 事務設定

你還可以輕鬆使用 Hibernate 本地事務,如下例所示。在這種情況下,你需要定義一個 Hibernate LocalSessionFactoryBean,你的應用程式碼可以使用它來獲取 Hibernate Session 例項。

DataSource bean 定義類似於之前顯示的本地 JDBC 示例,因此在以下示例中未顯示。

如果 DataSource(由任何非 JTA 事務管理器使用)透過 JNDI 查詢並由 Jakarta EE 容器管理,它應該是非事務性的,因為事務是由 Spring Framework(而不是 Jakarta EE 容器)管理的。

在這種情況下,txManager bean 的型別是 HibernateTransactionManager。與 DataSourceTransactionManager 需要引用 DataSource 一樣,HibernateTransactionManager 需要引用 SessionFactory。以下示例聲明瞭 sessionFactorytxManager bean

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mappingResources">
		<list>
			<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
		</list>
	</property>
	<property name="hibernateProperties">
		<value>
			hibernate.dialect=${hibernate.dialect}
		</value>
	</property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果你使用 Hibernate 和 Jakarta EE 容器管理的 JTA 事務,你應該使用與之前 JDBC 的 JTA 示例中相同的 JtaTransactionManager,如下例所示。另外,建議透過其事務協調器以及可能的連線釋放模式配置使 Hibernate 瞭解 JTA

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mappingResources">
		<list>
			<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
		</list>
	</property>
	<property name="hibernateProperties">
		<value>
			hibernate.dialect=${hibernate.dialect}
			hibernate.transaction.coordinator_class=jta
			hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
		</value>
	</property>
</bean>

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

或者,你也可以將 JtaTransactionManager 傳遞給你的 LocalSessionFactoryBean,以強制執行相同的預設設定

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mappingResources">
		<list>
			<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
		</list>
	</property>
	<property name="hibernateProperties">
		<value>
			hibernate.dialect=${hibernate.dialect}
		</value>
	</property>
	<property name="jtaTransactionManager" ref="txManager"/>
</bean>

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>