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) と同様に定義されます。この利点だけでも、JTA を使用する場合でも、Spring Framework トランザクションは価値のある抽象化になります。JTA を直接使用する場合よりもはるかに簡単にトランザクションコードをテストできます。

再び、Spring の哲学に沿って、PlatformTransactionManager インターフェースのメソッドのいずれかによってスローされる TransactionException はチェックされていません(つまり、java.lang.RuntimeException クラスを継承します)。トランザクションインフラストラクチャの障害は、ほぼ常に致命的です。アプリケーションコードがトランザクション障害から実際に回復できるまれなケースでは、アプリケーション開発者は TransactionException をキャッチして処理することを選択できます。重要な点は、開発者がそうすること強制されていないということです。

getTransaction(..) メソッドは、TransactionDefinition パラメーターに応じて、TransactionStatus オブジェクトを返します。返された TransactionStatus は、現在の呼び出しスタックに一致するトランザクションが存在する場合、新しいトランザクションを表すか、既存のトランザクションを表すことができます。後者の場合の意味は、Jakarta EE トランザクションコンテキストと同様に、TransactionStatus は実行スレッドに関連付けられているということです。

Spring Framework 5.2 の時点で、Spring は、リアクティブ型または Kotlin コルーチンを利用するリアクティブアプリケーションのトランザクション管理の抽象化も提供します。次のリストは、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 インターフェースは以下を指定します。

  • 伝搬: 通常、トランザクションスコープ内のすべてのコードは、そのトランザクションで実行されます。ただし、トランザクションコンテキストがすでに存在するときにトランザクションメソッドが実行される場合の動作を指定できます。例: コードは既存のトランザクションで実行を継続できます(一般的なケース)。または、既存のトランザクションを中断して新しいトランザクションを作成できます。Spring は、EJB CMT でよく知られているすべてのトランザクション伝播オプションを提供します。Spring でのトランザクション伝播のセマンティクスについては、トランザクションの伝播を参照してください。

  • 分離: このトランザクションが他のトランザクションの作業から分離される程度。例: このトランザクションは、他のトランザクションからのコミットされていない書き込みを見ることができますか?

  • タイムアウト: タイムアウトし、基礎となるトランザクションインフラストラクチャによって自動的にロールバックされるまでに、このトランザクションが実行される時間。

  • 読み取り専用状態: コードが読み取りを行うがデータを変更しない場合は、読み取り専用トランザクションを使用できます。読み取り専用トランザクションは、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 を使用する場合、Spring の JtaTransactionManager とともに、JNDI から取得したコンテナー DataSource を使用します。次の例は、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 スキーマを参照してください。
JTA を使用する場合、使用するデータアクセステクノロジー(JDBC、Hibernate JPA、またはその他のサポートされるテクノロジー)に関係なく、トランザクションマネージャーの定義は同じように見えるはずです。これは、JTA トランザクションがすべてのトランザクションリソースを登録できるグローバルトランザクションであるためです。

すべての Spring トランザクション設定で、アプリケーションコードを変更する必要はありません。構成を変更するだけでトランザクションの管理方法を変更できます。その変更がローカルトランザクションからグローバルトランザクションへの移行、またはその逆を意味する場合でも同様です。

Hibernate トランザクション設定

次の例に示すように、Hibernate ローカルトランザクションを簡単に使用することもできます。この場合、アプリケーションコードが Hibernate Session インスタンスを取得するために使用できる Hibernate LocalSessionFactoryBean を定義する必要があります。

DataSource Bean 定義は、前に示したローカル JDBC の例と似ているため、以下の例には示されていません。

DataSource (非 JTA トランザクションマネージャーによって使用される)が JNDI を通じてルックアップされ、Jakarta EE コンテナーによって管理される場合、Spring Framework(Jakarta EE コンテナーではなく)がトランザクションを管理するため、非トランザクションである必要があります。

この場合、txManager Bean は HibernateTransactionManager 型です。DataSourceTransactionManager が DataSource への参照を必要とするのと同じ方法で、HibernateTransactionManager は SessionFactory への参照を必要とします。次の例では、sessionFactory および txManager 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"/>