宣言的トランザクション実装の例

以下のインターフェースとそれに付随する実装を検討してください。この例では、Foo クラスと Bar クラスをプレースホルダーとして使用しているため、特定のドメインモデルに焦点を当てずにトランザクションの使用に集中できます。この例の目的のために、DefaultFooService クラスが実装された各メソッドの本体で UnsupportedOperationException インスタンスをスローするという事実は良いことです。この動作により、トランザクションが作成され、UnsupportedOperationException インスタンスにレスポンスしてロールバックされるのを確認できます。次のリストは、FooService インターフェースを示しています。

  • Java

  • Kotlin

// the service interface that we want to make transactional

package x.y.service;

public interface FooService {

	Foo getFoo(String fooName);

	Foo getFoo(String fooName, String barName);

	void insertFoo(Foo foo);

	void updateFoo(Foo foo);

}
// the service interface that we want to make transactional

package x.y.service

interface FooService {

	fun getFoo(fooName: String): Foo

	fun getFoo(fooName: String, barName: String): Foo

	fun insertFoo(foo: Foo)

	fun updateFoo(foo: Foo)
}

次の例は、前述のインターフェースの実装を示しています。

  • Java

  • Kotlin

package x.y.service;

public class DefaultFooService implements FooService {

	@Override
	public Foo getFoo(String fooName) {
		// ...
	}

	@Override
	public Foo getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public void insertFoo(Foo foo) {
		// ...
	}

	@Override
	public void updateFoo(Foo foo) {
		// ...
	}
}
package x.y.service

class DefaultFooService : FooService {

	override fun getFoo(fooName: String): Foo {
		// ...
	}

	override fun getFoo(fooName: String, barName: String): Foo {
		// ...
	}

	override fun insertFoo(foo: Foo) {
		// ...
	}

	override fun updateFoo(foo: Foo) {
		// ...
	}
}

FooService インターフェースの最初の 2 つのメソッド getFoo(String) および getFoo(String, String) は、読み取り専用のセマンティクスを持つトランザクションのコンテキストで実行する必要があり、他のメソッド insertFoo(Foo) および updateFoo(Foo) は、読み取り / 書き込みを持つトランザクションのコンテキストで実行する必要があると仮定します。セマンティクス。次の構成について、次のいくつかの段落で詳しく説明します。

<!-- from the file 'context.xml' -->
<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/tx
		https://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- this is the service object that we want to make transactional -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>

	<!-- 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>

	<!-- ensure that the above transactional advice runs for any execution
		of an operation defined by the FooService interface -->
	<aop:config>
		<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
	</aop:config>

	<!-- don't forget the DataSource -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
		<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
		<property name="username" value="scott"/>
		<property name="password" value="tiger"/>
	</bean>

	<!-- similarly, don't forget the TransactionManager -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>

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

</beans>

上記の構成を調べます。サービスオブジェクトである fooService Bean をトランザクションにしたいと想定しています。適用するトランザクションセマンティクスは、<tx:advice/> 定義にカプセル化されています。<tx:advice/> 定義は、「 get で始まるすべてのメソッドは読み取り専用トランザクションのコンテキストで実行され、他のすべてのメソッドはデフォルトのトランザクションセマンティクスで実行される」と解釈されます。<tx:advice/> タグの transaction-manager 属性は、トランザクションを駆動する TransactionManager Bean(この場合は txManager Bean)の名前に設定されます。

ワイヤリングする TransactionManager の Bean 名の名前が transactionManager である場合、トランザクションアドバイス(<tx:advice/>)の transaction-manager 属性を省略できます。接続する TransactionManager Bean に他の名前がある場合、前述の例のように、transaction-manager 属性を明示的に使用する必要があります。

<aop:config/> 定義は、txAdvice Bean によって定義されたトランザクションアドバイスがプログラムの適切なポイントで実行されるようにします。まず、FooService インターフェース(fooServiceOperation)で定義された操作の実行と一致するポイントカットを定義します。次に、アドバイザーを使用して、ポイントカットを txAdvice に関連付けます。結果は、fooServiceOperation の実行時に、txAdvice によって定義されたアドバイスが実行されることを示しています。

<aop:pointcut/> 要素内で定義された式は、AspectJ ポイントカット式です。Spring のポイントカット式の詳細については、 "AOP" セクションを参照してください。

一般的な要件は、サービス層全体をトランザクション化することです。これを行う最善の方法は、ポイントカット式を変更して、サービスレイヤー内の任意の操作に一致させることです。次の例は、その方法を示しています。

<aop:config>
	<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
	<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
前述の例では、すべてのサービスインターフェースが x.y.service パッケージで定義されていることを前提としています。詳細については、"AOP" セクションを参照してください。

これで構成を分析したため、「この構成はすべて実際に何をしているのですか?」

前に示した構成は、fooService Bean 定義から作成されたオブジェクトの周囲にトランザクションプロキシを作成するために使用されます。プロキシは、適切なメソッドがプロキシで呼び出されると、そのメソッドに関連付けられたトランザクション構成に応じて、トランザクションが開始、一時停止、読み取り専用などのように、トランザクションアドバイスで構成されます。前に示した構成をテスト駆動する次のプログラムを検討してください。

  • Java

  • Kotlin

public final class Boot {

	public static void main(final String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
		FooService fooService = ctx.getBean(FooService.class);
		fooService.insertFoo(new Foo());
	}
}
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = ClassPathXmlApplicationContext("context.xml")
	val fooService = ctx.getBean<FooService>("fooService")
	fooService.insertFoo(Foo())
}

上記のプログラムの実行による出力は次のようになります(DefaultFooService クラスの insertFoo(..) メソッドによってスローされた UnsupportedOperationException からの Log4J 出力およびスタックトレースは、明確にするために省略されています)。

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

リアクティブトランザクション管理を使用するには、コードでリアクティブ型を使用する必要があります。

Spring Framework は ReactiveAdapterRegistry を使用して、メソッドの戻り値の型がリアクティブかどうかを判断します。

以下のリストは、以前に使用された FooService の変更バージョンを示していますが、今回はコードがリアクティブ型を使用しています。

  • Java

  • Kotlin

// the reactive service interface that we want to make transactional

package x.y.service;

public interface FooService {

	Flux<Foo> getFoo(String fooName);

	Publisher<Foo> getFoo(String fooName, String barName);

	Mono<Void> insertFoo(Foo foo);

	Mono<Void> updateFoo(Foo foo);

}
// the reactive service interface that we want to make transactional

package x.y.service

interface FooService {

	fun getFoo(fooName: String): Flow<Foo>

	fun getFoo(fooName: String, barName: String): Publisher<Foo>

	fun insertFoo(foo: Foo) : Mono<Void>

	fun updateFoo(foo: Foo) : Mono<Void>
}

次の例は、前述のインターフェースの実装を示しています。

  • Java

  • Kotlin

package x.y.service;

public class DefaultFooService implements FooService {

	@Override
	public Flux<Foo> getFoo(String fooName) {
		// ...
	}

	@Override
	public Publisher<Foo> getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public Mono<Void> insertFoo(Foo foo) {
		// ...
	}

	@Override
	public Mono<Void> updateFoo(Foo foo) {
		// ...
	}
}
package x.y.service

class DefaultFooService : FooService {

	override fun getFoo(fooName: String): Flow<Foo> {
		// ...
	}

	override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
		// ...
	}

	override fun insertFoo(foo: Foo): Mono<Void> {
		// ...
	}

	override fun updateFoo(foo: Foo): Mono<Void> {
		// ...
	}
}

命令型とリアクティブ型のトランザクション管理は、トランザクション境界とトランザクション属性の定義で同じセマンティクスを共有します。命令型トランザクションとリアクティブトランザクションの主な違いは、後者の遅延性質です。TransactionInterceptor は、返されたリアクティブ型をトランザクションオペレーターで装飾して、トランザクションを開始およびクリーンアップします。トランザクションのリアクティブメソッドを呼び出すと、実際のトランザクション管理は、リアクティブ型の処理をアクティブにするサブスクリプション型に委ねられます。

リアクティブトランザクション管理の別の側面は、プログラミングモデルの自然な結果であるデータエスケープに関連しています。

命令型トランザクションのメソッド戻り値は、メソッドの正常終了時にトランザクションメソッドから返されるため、部分的に計算された結果がメソッドクロージャをエスケープしません。

リアクティブトランザクションメソッドは、計算シーケンスを表すリアクティブラッパー型と、計算を開始して完了するという約束を返します。

Publisher は、トランザクションの進行中にデータを送信できますが、必ずしも完了しているとは限りません。トランザクション全体の正常な補完に依存するメソッドは、完了を確実にし、呼び出しコードで結果をバッファリングする必要があります。