プログラムによるトランザクション管理

Spring Framework は、以下を使用することにより、プログラムによるトランザクション管理の 2 つの手段を提供します。

  • TransactionTemplate または TransactionalOperator

  • TransactionManager 実装。

Spring チームは通常、命令フローでのプログラムによるトランザクション管理には TransactionTemplate を、リアクティブコードには TransactionalOperator を推奨しています。2 番目のアプローチは、JTA UserTransaction API を使用する場合と似ていますが、例外処理はそれほど面倒ではありません。

TransactionTemplate を使用する

TransactionTemplate は、JdbcTemplate などの他の Spring テンプレートと同じアプローチを採用しています。コールバックアプローチ(ボイラープレートの取得を実行してトランザクションリソースを解放する必要からアプリケーションコードを解放する)を使用し、コードが実行したいことにのみ焦点を当てるという点で、意図に基づいたコードになります。

以下の例が示すように、TransactionTemplate を使用すると、Spring のトランザクションインフラストラクチャと API に完全に結合されます。プログラマティックトランザクション管理が開発ニーズに適しているかどうかは、あなた自身で決定する必要があります。

トランザクションコンテキストで実行する必要があり、TransactionTemplate を明示的に使用するアプリケーションコードは、次の例のようになります。アプリケーション開発者は、トランザクションのコンテキストで実行する必要があるコードを含む TransactionCallback 実装(通常は匿名の内部クラスとして表現)を作成できます。次に、カスタム TransactionCallback のインスタンスを TransactionTemplate で公開されている execute(..) メソッドに渡すことができます。次の例は、その方法を示しています。

public class SimpleService implements Service {

	// single TransactionTemplate shared amongst all methods in this instance
	private final TransactionTemplate transactionTemplate;

	// use constructor-injection to supply the PlatformTransactionManager
	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);
	}

	public Object someServiceMethod() {
		return transactionTemplate.execute(new TransactionCallback() {
			// the code in this method runs in a transactional context
			public Object doInTransaction(TransactionStatus status) {
				updateOperation1();
				return resultOfUpdateOperation2();
			}
		});
	}
}

戻り値がない場合、次のように匿名クラスで便利な TransactionCallbackWithoutResult クラスを使用できます。

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
	protected void doInTransactionWithoutResult(TransactionStatus status) {
		updateOperation1();
		updateOperation2();
	}
});

コールバック内のコードは、次のように、提供された TransactionStatus オブジェクトで setRollbackOnly() メソッドを呼び出すことにより、トランザクションをロールバックできます。

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

	protected void doInTransactionWithoutResult(TransactionStatus status) {
		try {
			updateOperation1();
			updateOperation2();
		} catch (SomeBusinessException ex) {
			status.setRollbackOnly();
		}
	}
});

トランザクション設定の指定

TransactionTemplate のトランザクション設定 (伝播モード、分離レベル、タイムアウトなど) は、プログラムまたは構成で指定できます。デフォルトでは、TransactionTemplate インスタンスにはデフォルトのトランザクション設定が適用されます。次の例は、特定の TransactionTemplate: のトランザクション設定のプログラムによるカスタマイズを示しています。

public class SimpleService implements Service {

	private final TransactionTemplate transactionTemplate;

	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);

		// the transaction settings can be set here explicitly if so desired
		this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		this.transactionTemplate.setTimeout(30); // 30 seconds
		// and so forth...
	}
}

次の例では、Spring XML 構成を使用して、いくつかのカスタムトランザクション設定で TransactionTemplate を定義しています。

<bean id="sharedTransactionTemplate"
		class="org.springframework.transaction.support.TransactionTemplate">
	<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
	<property name="timeout" value="30"/>
</bean>

その後、sharedTransactionTemplate を必要な数のサービスに挿入できます。

最後に、TransactionTemplate クラスのインスタンスはスレッドセーフであり、そのインスタンスは会話状態を維持しません。ただし、TransactionTemplate インスタンスは構成状態を維持します。そのため、多くのクラスが TransactionTemplate の単一のインスタンスを共有する場合がありますが、クラスが異なる設定(たとえば、異なる分離レベル)で TransactionTemplate を使用する必要がある場合、2 つの異なる TransactionTemplate インスタンスを作成する必要があります。

TransactionalOperator を使用する

TransactionalOperator は、他のリアクティブオペレーターと同様のオペレーター設計に従います。コールバックアプローチ(ボイラープレートの取得を実行してトランザクションリソースを解放する必要からアプリケーションコードを解放する)を使用し、コードが実行したいことにのみ焦点を当てるという点で、意図に基づいたコードになります。

以下の例が示すように、TransactionalOperator を使用すると、Spring のトランザクションインフラストラクチャと API に完全に結合されます。プログラマティックトランザクション管理が開発ニーズに適しているかどうかは、あなた自身で決定する必要があります。

トランザクションコンテキストで実行する必要があり、TransactionalOperator を明示的に使用するアプリケーションコードは、次の例のようになります。

public class SimpleService implements Service {

	// single TransactionalOperator shared amongst all methods in this instance
	private final TransactionalOperator transactionalOperator;

	// use constructor-injection to supply the ReactiveTransactionManager
	public SimpleService(ReactiveTransactionManager transactionManager) {
		this.transactionalOperator = TransactionalOperator.create(transactionManager);
	}

	public Mono<Object> someServiceMethod() {

		// the code in this method runs in a transactional context

		Mono<Object> update = updateOperation1();

		return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
	}
}

TransactionalOperator は 2 つの方法で使用できます。

  • プロジェクト Reactor 型を使用するオペレータースタイル (mono.as(transactionalOperator::transactional))

  • 他のすべての場合のコールバックスタイル (transactionalOperator.execute(TransactionCallback<T>))

コールバック内のコードは、次のように、提供された ReactiveTransaction オブジェクトで setRollbackOnly() メソッドを呼び出すことにより、トランザクションをロールバックできます。

transactionalOperator.execute(new TransactionCallback<>() {

	public Mono<Object> doInTransaction(ReactiveTransaction status) {
		return updateOperation1().then(updateOperation2)
					.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
		}
	}
});

シグナルをキャンセル

Reactive Streams では、Subscriber は Subscription をキャンセルし、Publisher を停止できます。プロジェクト Reactor および next()take(long)timeout(Duration) などの他のライブラリのオペレーターは、キャンセルを発行できます。それがエラーによるものか、それ以上消費するだけの関心がないためか、キャンセルの理由を知る方法はありません。バージョン 5.3 以降のキャンセルシグナルはロールバックにつながります。その結果、トランザクション Publisher のダウンストリームで使用される演算子を考慮することが重要です。特に Flux または他の多値 Publisher の場合、トランザクションを完了するには、出力全体を消費する必要があります。

トランザクション設定の指定

TransactionalOperator のトランザクション設定 (伝播モード、分離レベル、タイムアウトなど) を指定できます。デフォルトでは、TransactionalOperator インスタンスにはデフォルトのトランザクション設定があります。次の例は、特定の TransactionalOperator: のトランザクション設定のカスタマイズを示しています。

public class SimpleService implements Service {

	private final TransactionalOperator transactionalOperator;

	public SimpleService(ReactiveTransactionManager transactionManager) {
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

		// the transaction settings can be set here explicitly if so desired
		definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		definition.setTimeout(30); // 30 seconds
		// and so forth...

		this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
	}
}

TransactionManager を使用する

以下のセクションでは、命令型およびリアクティブ型トランザクションマネージャーのプログラムによる使用箇所について説明します。

PlatformTransactionManager を使用する

命令型トランザクションの場合、org.springframework.transaction.PlatformTransactionManager を直接使用してトランザクションを管理できます。そのためには、使用する PlatformTransactionManager の実装を Bean 参照を通じて Bean に渡します。次に、TransactionDefinition および TransactionStatus オブジェクトを使用して、トランザクションを開始し、ロールバックしてコミットできます。次の例は、その方法を示しています。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
	// put your business logic here
} catch (MyException ex) {
	txManager.rollback(status);
	throw ex;
}
txManager.commit(status);

ReactiveTransactionManager を使用する

リアクティブトランザクションを使用する場合、org.springframework.transaction.ReactiveTransactionManager を直接使用してトランザクションを管理できます。そのためには、使用する ReactiveTransactionManager の実装を Bean 参照を通じて Bean に渡します。次に、TransactionDefinition および ReactiveTransaction オブジェクトを使用して、トランザクションを開始し、ロールバックしてコミットできます。次の例は、その方法を示しています。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

	Mono<Object> tx = ...; // put your business logic here

	return tx.then(txManager.commit(status))
			.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});