Spring Frameworkは、依存性注入コンテナに加えて、AOP、データアクセス、トランザクション、メッセージング、Webアプリケーションに特化したモジュールを提供します。トランザクション管理は、フレームワークの統合モデルを示す代表例です。アプリケーションコードが境界を宣言し、インターセプターがリソース固有のPlatformTransactionManagerまたはリアクティブトランザクションマネージャーを調整します。
プロキシベースの宣言的トランザクション #
デフォルトのアドバイスモードでは、Springは対象となるBeanをAOPプロキシでラップします。@Transactionalに一致するメソッドへの呼び出しはTransactionInterceptorを経由し、トランザクションメタデータを読み取り、トランザクションを開始または既存のトランザクションに参加し、対象メソッドを呼び出した後、コミットまたはロールバックします。
@Service
class OrderService {
private final OrderRepository orders;
private final PaymentGateway payments;
@Transactional
public Order place(CreateOrder command) {
Order order = orders.insert(command);
payments.reserve(order);
return order;
}
}プロキシには実際上の制約があります。呼び出しはプロキシを経由して入る必要があり、メソッドは選択したプロキシ機構によってインターセプト可能でなければなりません。インターフェースベースのJDKプロキシはインターフェースのメソッドを公開します。クラスベースのプロキシは対象をサブクラス化するため、finalメソッドにはアドバイスを適用できません。
自己呼び出し #
this.reprice()のような呼び出しは対象オブジェクト内にとどまり、そのプロキシには再び入りません。そのため、reprice()への到達が自己呼び出しだけによる場合、そのメソッドに付与されたトランザクションメタデータは無視されます。
public void importOrders(List<Order> batch) {
batch.forEach(this::storeOne); // storeOneのトランザクションアドバイスを回避する
}トランザクション処理が実際の境界を表す場合は、その処理を別の注入可能なサービスへ移します。プログラムによるトランザクション管理やAspectJのウィービングも代替手段です。Bean自身のプロキシを注入すると、ドメインコードがコンテナの仕組みに結合され、テストも難しくなります。
境界の粒度 #
トランザクションには、アプリケーションレベルで1つの整合性操作を含めるべきです。コントローラーレベルの境界は、シリアライズや無関係なネットワーク処理まで含みがちです。一方、リポジトリレベルの境界では、1つのビジネス操作が複数のコミットに分割される可能性があります。サービスメソッドは通常、ドメイン不変条件を中心に複数のリポジトリを調整するため、有用な単位になります。
ロック保持時間、リトライ、障害復旧を分析せずに、制御不能なリモート呼び出しをまたいでデータベーストランザクションを開いたままにしないでください。ローカルトランザクションによって外部サービスをアトミックにすることはできません。outbox、冪等性、またはSaga形式の調整が必要になる場合があります。
伝播、分離、ロールバック #
伝播は、メソッドが既存のトランザクションとどのように関係するかを定義します。
REQUIREDはデフォルトで、既存のトランザクションに参加するか、新しいトランザクションを作成します。REQUIRES_NEWは既存のトランザクションを中断して独立したトランザクションを開始します。別のプール接続が必要になる場合があります。SUPPORTSは既存のトランザクションがあれば参加し、なければトランザクションなしで実行します。MANDATORYは既存のトランザクションを要求し、NEVERはトランザクションを禁止します。NOT_SUPPORTEDは既存のトランザクションを中断します。NESTEDは、トランザクションマネージャーとリソースがセーブポイントをサポートしている場合、1つの物理トランザクション内でセーブポイントを使用します。
分離レベルの要求は、リソースの機能に対応付けられます。DEFAULTは設定済みのデフォルト値を使用し、それ以外の値は標準的なJDBC分離レベルに対応します。宣言したからといって、すべてのデータベースが同一のロック方式やスナップショットセマンティクスを実装しているとは限りません。そのため、選択したデータベースとドライバーで動作を検証してください。
デフォルトでは、SpringはRuntimeExceptionとErrorでトランザクションをロールバック対象としてマークしますが、チェック例外ではそうしません。例外分類の意味が異なる場合は、rollbackForまたはnoRollbackForを設定してください。内部の参加者が共有トランザクションをロールバック専用としてマークすると、外側ではコミットに見える処理がUnexpectedRollbackExceptionを発生させることがあります。
アドバイザーの順序 #
セキュリティ、リトライ、キャッシュ、メトリクス、ロック、トランザクションの関心事は、同じ呼び出しをすべてラップできます。入れ子の順序によって意味が変わります。1つのトランザクション内でリトライすることは、試行ごとに新しいトランザクションを開くことと同じではありません。また、認可の前にキャッシュするとデータが公開される可能性があります。サポートされている場合はOrdered/@Orderを使用して順序を指定し、結果として得られる呼び出し順序をテストしてください。必要な順序が明示されていない場合は、アノテーションを積み重ねないでください。
宣言的プロキシの代替手段 #
必要な境界に応じて、次の3つのアプローチから選択します。
- 操作を別のサービスにリファクタリングし、宣言的アドバイスを維持する。
TransactionTemplateを使用し、可視性の高い制御フローと例外処理を備えた明示的なローカルブロックを作成する。- ウィービングされた自己呼び出しアドバイスをシステム全体の意図的な選択とする場合は、
@TransactionalAspectJモードを有効にする。
Order saved = transactionTemplate.execute(status -> orders.insert(command));
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)