@Transactional を使用する

トランザクション構成への XML ベースの宣言的アプローチに加えて、アノテーションベースのアプローチを使用できます。Java ソースコードでトランザクションセマンティクスを直接宣言すると、影響を受けるコードにより近い宣言になります。過度な結合の危険性はあまりありません。トランザクションで使用することを目的としたコードは、ほとんどの場合、そのようにデプロイされるためです。

標準の jakarta.transaction.Transactional アノテーションは、Spring 独自のアノテーションのドロップイン置換としてもサポートされています。詳細については、JTA のドキュメントを参照してください。

@Transactional アノテーションを使用することで得られる使いやすさは、以下のテキストで説明されている例で最もよく説明されています。次のクラス定義を検討してください。

  • Java

  • Kotlin

// the service class that we want to make transactional
@Transactional
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) {
		// ...
	}
}
// the service class that we want to make transactional
@Transactional
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) {
		// ...
	}
}

上記のようにクラスレベルで使用されるアノテーションは、宣言クラス (およびそのサブクラス) のすべてのメソッドのデフォルトを示します。あるいは、各メソッドに個別にアノテーションを付けることもできます。Spring がどのメソッドをトランザクションとみなしているかの詳細については、「メソッドの可視性」を参照してください。クラスレベルのアノテーションは、クラス階層の上の祖先クラスには適用されないことに注意してください。このようなシナリオでは、サブクラスレベルのアノテーションに参加するために、継承されたメソッドをローカルで再宣言する必要があります。

上記のような POJO クラスが Spring コンテキストで Bean として定義されている場合、Bean インスタンスを @Configuration クラスの @EnableTransactionManagement アノテーションを介してトランザクション対応にすることができます。詳細については、javadoc を参照してください。

XML 構成では、<tx:annotation-driven/> タグは同様の利便性を提供します。

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

	<!-- enable the configuration of transactional behavior based on annotations -->
	<!-- a TransactionManager is still required -->
	<tx:annotation-driven transaction-manager="txManager"/> (1)

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- (this dependency is defined somewhere else) -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

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

</beans>
1Bean インスタンスをトランザクション化する行。
接続する TransactionManager の Bean 名の名前が transactionManager の場合は、<tx:annotation-driven/> タグの transaction-manager 属性を省略できます。依存性注入する TransactionManager Bean に他の名前がある場合は、前の例のように transaction-manager 属性を使用する必要があります。

リアクティブトランザクションメソッドは、次のように、命令型プログラミングの配置とは対照的に、リアクティブ戻り値の型を使用します。

  • Java

  • Kotlin

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

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

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

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

	@Override
	public Mono<Void> updateFoo(Foo foo) {
		// ...
	}
}
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {

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

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

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

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

Reactive Streams キャンセルシグナルに関して、返される Publisher には特別な考慮事項があることに注意してください。詳細については、「TransactionalOperator の使用」のシグナルをキャンセルセクションを参照してください。

プロキシモードでのメソッドの可視性と @Transactional 

@Transactional アノテーションは通常、public 可視性を持つメソッドで使用されます。6.0 以降、protected またはパッケージ可視メソッドも、デフォルトでクラスベースのプロキシに対してトランザクションにすることができます。インターフェースベースのプロキシのトランザクションメソッドは常に public であり、プロキシされたインターフェースで定義されている必要があることに注意してください。どちらの種類のプロキシでも、プロキシ経由で受信する外部メソッド呼び出しのみがインターセプトされます。

さまざまな種類のプロキシ間でメソッドの可視性を一貫して処理したい場合 (5.3 まではデフォルトでした)、publicMethodsOnly を指定することを検討してください。

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to true to consistently ignore non-public methods.
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
	return new AnnotationTransactionAttributeSource(true);
}

Spring TestContext フレームワークは、非プライベート @Transactional テストメソッドもデフォルトでサポートします。例については、テストの章のトランザクション管理を参照してください。

@Transactional アノテーションは、インターフェース定義、インターフェース上のメソッド、クラス定義、クラス上のメソッドに適用できます。ただし、@Transactional アノテーションが存在するだけでは、トランザクション動作をアクティブにするのに十分ではありません。@Transactional アノテーションは、そのメタデータを使用してトランザクション動作を伴う適切な Bean を構成する、対応するランタイムインフラストラクチャによって使用できる単なるメタデータです。前述の例では、<tx:annotation-driven/> 要素は実行時に実際のトランザクション管理をオンにします。

Spring チームは、5.0 の時点でインターフェースベースのプロキシとターゲットクラスのプロキシに対して後者が機能する場合でも、インターフェース内のアノテーション付きメソッドに依存するのではなく、具象クラスのメソッドに @Transactional アノテーションを付けることをお勧めします。Java アノテーションはインターフェースから継承されないため、AspectJ モードを使用する場合、インターフェースで宣言されたアノテーションは依然としてウィービングインフラストラクチャによって認識されず、そのためアスペクトは適用されません。その結果、トランザクションのアノテーションが確認なしで無視される可能性があります。ロールバックシナリオをテストするまで、コードは「動作している」ように見える可能性があります。
プロキシモード(デフォルト)では、プロキシを介して受信する外部メソッド呼び出しのみがインターセプトされます。つまり、自己呼び出し(実際には、ターゲットオブジェクト内のメソッドがターゲットオブジェクトの別のメソッドを呼び出す)は、呼び出されたメソッドが @Transactional でマークされていても、実行時に実際のトランザクションにつながりません。また、期待される動作を提供するには、プロキシを完全に初期化する必要があるため、初期化コード(@PostConstruct メソッドなど)でこの機能に依存しないでください。

自己呼び出しもトランザクションでラップされることが予想される場合は、AspectJ モード(次の表の mode 属性を参照)の使用を検討してください。この場合、そもそもプロキシはありません。代わりに、ターゲットクラスは、あらゆる種類のメソッドで @Transactional ランタイム動作をサポートするように織り込まれています(つまり、そのバイトコードが変更されています)。

表 1: アノテーション駆動のトランザクション設定
XML 属性 アノテーション属性 デフォルト 説明

transaction-manager

なし (TransactionManagementConfigurer javadoc を参照)

transactionManager

使用するトランザクションマネージャーの名前。前の例のように、トランザクションマネージャーの名前が transactionManager でない場合にのみ必要です。

mode

mode

proxy

デフォルトモード(proxy)は、Spring の AOP フレームワークを使用して、プロキシ化されるアノテーション付き Bean を処理します(前述のプロキシセマンティクスに従い、プロキシ経由で受信するメソッド呼び出しにのみ適用されます)。代替モード(aspectj)は、代わりに、影響を受けるクラスを Spring の AspectJ トランザクションアスペクトで織り込み、ターゲットクラスのバイトコードを変更して、あらゆる種類のメソッド呼び出しに適用します。AspectJ ウィービングでは、ロード時ウィービング(またはコンパイル時ウィービング)を有効にするとともに、クラスパスに spring-aspects.jar が必要です。(ロード時ウィービングを設定する方法の詳細については、Spring の構成を参照してください。)

proxy-target-class

proxyTargetClass

false

proxy モードのみに適用されます。@Transactional アノテーションが付けられたクラスに対して作成されるトランザクションプロキシの型を制御します。proxy-target-class 属性が true に設定されている場合、クラスベースのプロキシが作成されます。proxy-target-class が false の場合、または属性が省略された場合、標準の JDK インターフェースベースのプロキシが作成されます。(さまざまなプロキシ型の詳細な調査については、プロキシメカニズムを参照してください。)

order

order

Ordered.LOWEST_PRECEDENCE

@Transactional アノテーションが付けられた Bean に適用されるトランザクションアドバイスの順序を定義します。(AOP アドバイスの順序に関するルールの詳細については、アドバイスのオーダーを参照してください)順序が指定されていない場合、AOP サブシステムがアドバイスの順序を決定することを意味します。

@Transactional アノテーションを処理するためのデフォルトのアドバイスモードは proxy です。これにより、プロキシのみを介した呼び出しのインターセプトが可能になります。同じクラス内のローカル呼び出しは、そのようにインターセプトすることはできません。より高度なインターセプトモードについては、コンパイル時またはロード時のウィービングと組み合わせて aspectj モードに切り替えることを検討してください。
proxy-target-class 属性は、@Transactional アノテーションが付けられたクラスに対して作成されるトランザクションプロキシの型を制御します。proxy-target-class が true に設定されている場合、クラスベースのプロキシが作成されます。proxy-target-class が false の場合、または属性が省略された場合、標準の JDK インターフェースベースのプロキシが作成されます。(さまざまなプロキシ型の説明については、プロキシメカニズムを参照してください。)
@EnableTransactionManagement と <tx:annotation-driven/> は、それらが定義されているのと同じアプリケーションコンテキスト内の Bean でのみ @Transactional を検索します。つまり、DispatcherServlet の WebApplicationContext にアノテーション駆動型構成を配置すると、サービスではなくコントローラーでのみ @Transactional Bean がチェックされます。詳細については、MVC を参照してください。

メソッドのトランザクション設定を評価する場合、最も派生した場所が優先されます。次の例の場合、DefaultFooService クラスは読み取り専用トランザクションの設定でクラスレベルでアノテーションが付けられますが、同じクラスの updateFoo(Foo) メソッドの @Transactional アノテーションはクラスレベルで定義されたトランザクション設定より優先されます。

  • Java

  • Kotlin

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

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

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void updateFoo(Foo foo) {
		// ...
	}
}
@Transactional(readOnly = true)
class DefaultFooService : FooService {

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

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	override fun updateFoo(foo: Foo) {
		// ...
	}
}

@Transactional の設定

@Transactional アノテーションは、インターフェース、クラス、メソッドにトランザクションセマンティクスが必要であることを指定するメタデータです(たとえば、「このメソッドが呼び出されたときに新しい読み取り専用トランザクションを開始し、既存のトランザクションを一時停止します」)。デフォルトの @Transactional 設定は次のとおりです。

  • 伝播設定は PROPAGATION_REQUIRED. です

  • 分離レベルは ISOLATION_DEFAULT. です

  • トランザクションは読み書き可能です。

  • トランザクションタイムアウトは、基本となるトランザクションシステムのデフォルトタイムアウトにデフォルト設定されます。タイムアウトがサポートされていない場合は、なしに設定されます。

  • RuntimeException または Error はロールバックをトリガーしますが、チェックされた Exception はトリガーしません。

これらのデフォルト設定を変更できます。次の表は、@Transactional アノテーションのさまざまなプロパティをまとめたものです。

表 2: @Transactional の設定
プロパティ タイプ 説明

value

String

使用するトランザクションマネージャーを指定するオプションの修飾子。

transactionManager

String

value のエイリアス。

label

トランザクションに表現力豊かな説明を追加する String ラベルの配列。

ラベルは、実装固有の動作を実際のトランザクションに関連付けるためにトランザクションマネージャーによって評価される場合があります。

propagation

enum: Propagation

オプションの伝播設定。

isolation

enum: Isolation

オプションの分離レベル。REQUIRED または REQUIRES_NEW の伝播値にのみ適用されます。

timeout

int (in seconds of granularity)

Optional transaction timeout. Applies only to propagation values of REQUIRED or REQUIRES_NEW.

timeoutString

String (in seconds of granularity)

Alternative for specifying the timeout in seconds as a String value — for example, as a placeholder.

readOnly

boolean

読み取り / 書き込みトランザクションと読み取り専用トランザクション。REQUIRED または REQUIRES_NEW の値にのみ適用可能。

rollbackFor

Class オブジェクトの配列。Throwable. から派生する必要があります

ロールバックを引き起こす必要がある例外型のオプションの配列。

rollbackForClassName

例外名パターンの配列。

ロールバックを引き起こす必要がある例外名パターンのオプションの配列。

noRollbackFor

Class オブジェクトの配列。Throwable. から派生する必要があります

ロールバックを引き起こしてはならない例外型のオプションの配列。

noRollbackForClassName

例外名パターンの配列。

ロールバックを引き起こしてはならない例外名パターンのオプションの配列。

ロールバックルールのセマンティクス、パターン、パターンベースのロールバックルールの意図しない一致の可能性に関する警告の詳細については、ロールバックルールを参照してください。

6.2 以降、デフォルトのロールバック動作をグローバルに変更できます。たとえば、@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS) を使用すると、チェック例外を含む、トランザクション内で発生したすべての例外をロールバックできます。さらにカスタマイズするために、AnnotationTransactionAttributeSource はカスタムのデフォルトルール用の addDefaultRollbackRule(RollbackRuleAttribute) メソッドを提供します。

トランザクション固有のロールバックルールはデフォルトの動作をオーバーライドしますが、指定されていない例外に対しては選択されたデフォルトを保持することに注意してください。これは、Spring の @Transactional と JTA の jakarta.transaction.Transactional アノテーションの場合に当てはまります。

コミット動作を伴う EJB スタイルのビジネス例外に依存しない限り、(偶発的な) チェック例外が発生した場合でも、一貫したロールバックセマンティクスを実現するために ALL_EXCEPTIONS に切り替えることをお勧めします。また、チェック例外がまったく強制されない Kotlin ベースのアプリケーションに対しては、この切り替えを行うことをお勧めします。

現在、トランザクションの名前を明示的に制御することはできません。「名前」とは、トランザクションモニターおよびログ出力に表示されるトランザクション名を意味します。宣言型トランザクションの場合、トランザクション名は常に、トランザクション的に推奨されるクラスの完全修飾クラス名 + . + メソッド名になります。例: BusinessService クラスの handlePayment(..) メソッドがトランザクションを開始した場合、トランザクションの名前は com.example.BusinessService.handlePayment になります。

@Transactional を使用した複数のトランザクションマネージャー

ほとんどの Spring アプリケーションには単一のトランザクションマネージャーのみが必要ですが、単一のアプリケーションに複数の独立したトランザクションマネージャーが必要な場合があります。@Transactional アノテーションの value または transactionManager 属性を使用して、オプションで、使用する TransactionManager の ID を指定できます。これは、Bean 名またはトランザクションマネージャー Bean の修飾子値のいずれかです。例: 修飾子表記を使用して、次の Java コードをアプリケーションコンテキストで次のトランザクションマネージャー Bean 宣言と組み合わせることができます。

  • Java

  • Kotlin

public class TransactionalService {

	@Transactional("order")
	public void setSomething(String name) { ... }

	@Transactional("account")
	public void doSomething() { ... }

	@Transactional("reactive-account")
	public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {

	@Transactional("order")
	fun setSomething(name: String) {
		// ...
	}

	@Transactional("account")
	fun doSomething() {
		// ...
	}

	@Transactional("reactive-account")
	fun doSomethingReactive(): Mono<Void> {
		// ...
	}
}

以下のリストは、Bean 宣言を示しています。

<tx:annotation-driven/>

	<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="order"/>
	</bean>

	<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="account"/>
	</bean>

	<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
		...
		<qualifier value="reactive-account"/>
	</bean>

この場合、TransactionalService の個々のメソッドは、orderaccountreactive-account 修飾子によって区別された個別のトランザクションマネージャーで実行されます。特に修飾された TransactionManager Bean が見つからない場合でも、デフォルトの <tx:annotation-driven> ターゲット Bean 名 transactionManager が引き続き使用されます。

同じクラス上のすべてのトランザクションメソッドが同じ修飾子を共有する場合は、代わりに型レベルの org.springframework.beans.factory.annotation.Qualifier アノテーションを宣言することを検討してください。その値が特定のトランザクションマネージャーの修飾子の値 (または Bean 名) と一致する場合、そのトランザクションマネージャーは、@Transactional 自体の特定の修飾子なしでトランザクション定義に使用されます。

このような型レベルの修飾子は、具象クラスで宣言することができ、基本クラスからのトランザクション定義にも適用されます。これにより、修飾されていない基本クラスメソッドのデフォルトのトランザクションマネージャーの選択が実質的にオーバーライドされます。

最後になりましたが、このような型 レベルの Bean 修飾子は複数の目的に使用できます。たとえば、値が "order" の場合、オートワイヤーのターゲット Bean と関連するトランザクションマネージャー定義が同じ修飾子値を宣言している限り、オートワイヤーの目的 (オーダーリポジトリの識別) とトランザクションマネージャーの選択に使用できます。このような修飾子値は、型が一致する Bean のセット内で一意であるだけでよく、ID として機能する必要はありません。

カスタム構成アノテーション

多くの異なるメソッドで @Transactional で同じ属性を繰り返し使用していることがわかった場合、Spring のメタアノテーションサポートを使用すると、特定のユースケースに合わせてカスタム合成アノテーションを定義できます。例: 次のアノテーション定義を検討します。

  • Java

  • Kotlin

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx

前述のアノテーションにより、前のセクションの例を次のように記述できます。

  • Java

  • Kotlin

public class TransactionalService {

	@OrderTx
	public void setSomething(String name) {
		// ...
	}

	@AccountTx
	public void doSomething() {
		// ...
	}
}
class TransactionalService {

	@OrderTx
	fun setSomething(name: String) {
		// ...
	}

	@AccountTx
	fun doSomething() {
		// ...
	}
}

前の例では、構文を使用してトランザクションマネージャー修飾子とトランザクションラベルを定義しましたが、伝播動作、ロールバックルール、タイムアウト、その他の機能を含めることもできます。