Spring でのトランザクションによる非同期実行の処理: よくある落とし穴とその解決方法

Barbara Streisand
リリース: 2024-11-09 06:42:02
オリジナル
745 人が閲覧しました

Handling Asynchronous Execution with Transactions in Spring: A Common Pitfall and How to Solve It

最新の Spring アプリケーションでは、非同期実行とトランザクション動作を組み合わせるのが一般的です。ただし、Spring は非同期タスクとトランザクションを管理するため、メソッドに @Async および @Transactional(propagation = Propagation.REQUIRES_NEW) のアノテーションを付けると、予期しない動作が発生する可能性があります。

この記事では、この問題を詳しく調査し、非同期実行とトランザクション管理の両方を正しく処理するための解決策を示します。

問題: @Async と @Transactional(propagation = Propagation.REQUIRES_NEW)

次のコード スニペットを考えてみましょう:

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSomething() {
    // save-point one
    // save-point two
}

ログイン後にコピー
ログイン後にコピー

一見すると、すべてが期待どおりに機能しているように見えるかもしれません。ただし、この構成には、意図しない動作を引き起こす可能性のある重要な問題がいくつかあります。

舞台裏では何が起こっているのでしょうか?

  • @非同期アノテーション:

@Async アノテーションは、メソッドを別のスレッドで非同期に実行するように Spring に指示します。これは、メソッドが呼び出し元の元のスレッドでは実行されず、スレッド プール内の別のスレッドにオフロードされることを意味します。
Spring はプロキシを使用して非同期メソッドを管理します。 @Async のアノテーションが付けられたメソッドを呼び出すと、Spring は別のスレッドでメソッドを実行する内部 Executor に実行を委任します。

  • @Transactional(propagation = Propagation.REQUIRES_NEW) 注釈:

@Transactional(propagation = Propagation.REQUIRES_NEW) アノテーションは、既存のトランザクションに関係なく、メソッドに対して新しいトランザクションが開始されることを保証します。呼び出しスレッド内のアクティブなトランザクションを一時停止し、メソッドの新しいトランザクションを開始します。

Spring のトランザクション管理は通常、スレッドにバインドされています。つまり、トランザクション コンテキストは現在のスレッドに関連付けられています。

紛争

この問題は、@Async が別のスレッドでメソッドを実行し、Spring のトランザクション管理がトランザクションをバインドするスレッドに依存しているために発生します。メソッドが非同期で実行されると、呼び出し元のスレッドからのトランザクション コンテキストが新しいスレッドに伝播されず、次の問題が発生します。

  • @Transactional アノテーションは、非同期スレッドに新しいトランザクションを作成せず、トランザクション動作 (ロールバック、コミットなど) は正しく処理されません。
  • 非同期メソッドは元のトランザクション コンテキストの外で実行されているため、REQUIRES_NEW 伝播設定は適用されません。

解決策: 非同期実行とトランザクションの分離

この問題を解決するには、トランザクションを別のサービス メソッドで処理することで、非同期実行をトランザクション ロジックから切り離します。その方法は次のとおりです:

  • ステップ 1: トランザクション ロジック用の新しい同期サービスを作成する
    トランザクション ロジックを処理する新しいサービスを作成します。このメソッドは、トランザクション管理が期待どおりに機能することを保証するために (@Async なしで) 同期的に実行されます。

  • ステップ 2: 同期メソッドを非同期的に呼び出す
    その後、@Async を使用して、同期トランザクション メソッドを非同期的に呼び出すことができます。これにより、トランザクション ロジックがメイン スレッドで正しく処理され、非同期動作が維持されることが保証されます。

リファクタリングされたコードは次のようになります:

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSomething() {
    // save-point one
    // save-point two
}

ログイン後にコピー
ログイン後にコピー

どのように機能するのでしょうか?

リファクタリングされたソリューションでは、saveSomethingAsync() メソッドに @Async の注釈を付けることで非同期実行が実現されます。これは、saveSomethingAsync() が呼び出されるとき、Spring の非同期タスク エグゼキューターによって管理される別のスレッドで実行されることを意味します。これを別のスレッドで実行すると、メインスレッドは saveSomethingAsync() が完了するのを待たずに実行を継続できます。このアプローチは、長時間実行されるタスクをオフロードしたり、応答性を向上させたり、独立した操作を同時に処理したりするシナリオに有益です。

トランザクション動作の場合、TransactionalServicesaveSomething() メソッドには @Transactional(propagation = Propagation.REQUIRES_NEW) の注釈が付けられます。これにより、saveSomething() を呼び出すたびに、呼び出しメソッド内の既存のトランザクションから独立して新しいトランザクションが作成されるようになります。 REQUIRES_NEW 伝播は新しいトランザクションを開始し、既存のトランザクションを一時停止します。これにより、saveSomething() が分離されたトランザクション コンテキストで動作できるようになります。これは、元の呼び出しメソッドにトランザクションがある場合でも、saveSomething() は独自の別のトランザクション内で動作し、この操作に対してのみ制御されたコミットとロールバックが有効になることを意味します。

非同期実行をトランザクション ロジックから切り離すことで、トランザクション管理が期待どおりに機能することを保証します。この設定では、トランザクション コンテキストは saveSomething() メソッド内で正しく処理されたままですが、saveSomethingAsync() メソッドは別のスレッドで実行され続けます。この懸念の分離により、非同期処理と信頼性の高いトランザクション管理の両方の利点が得られ、同時に処理する場合でも独立した安全なデータ操作が可能になります。

このアプローチをいつ使用するか?

  • トランザクションの分離が重要な場合: 特定の操作が別のトランザクション (つまり REQUIRES_NEW) で実行されるようにする必要がある場合、このアプローチはうまく機能します。

  • 非同期操作: 非同期で実行する必要があるが、独自のトランザクション境界も必要とする、長時間実行される独立したタスクがある場合。

代替案: メッセージ キューを使用して完全な分離を行う

より高度な分離が必要な場合、または再試行、エラー処理、長時間実行プロセスを処理したい場合は、タスクを Kafka や RabbitMQ などのメッセージ キューにオフロードすることを検討してください。メッセージ キューを使用すると、各タスクが独自のコンテキストで実行され、トランザクションを独立して管理できるようになります。

以上がSpring でのトランザクションによる非同期実行の処理: よくある落とし穴とその解決方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート