Spring アノテーション @Transactional の失敗について話しましょう | JD Cloud テクニカル チーム

I.はじめに

うーん、またピットを踏む。今回の要求は主に、期限を過ぎた計算に対する要求タスクを最適化することであり、既存の計算タスクは実行に時間がかかりすぎます。今回の問題を簡単に説明します:プロジェクト内の複数のデータベースで操作を実行するとき、私たちが期待するのは、トランザクション全体をトランザクションにカプセル化し、すべて成功するかすべて失敗することです。最初のデータのステータス更新は成功しましたが、挿入は後続のデータが異常であるため、データテーブルをクエリした結果、データのステータスが正常に更新されていることがわかります

えーっと、コードをチェックしたところ、確かに @Transactional アノテーションが使用されていることがわかりました。そこで、インターネット上の関連情報を照会すると、Spring でトランザクション アノテーション @Transactional を使用すると、いくつかのシナリオでアノテーションが失敗する、つまり期待どおりにトランザクション操作にカプセル化できないことが判明したため、アノテーションを検討しました関連する障害シナリオが分析され、記事は次のように構成されます。

2. @Transactional アノテーション失敗シナリオのインスタンスの検証

1. @Transactional アノテーション属性

属性 タイプ 説明
価値 使用するトランザクション マネージャーを指定するオプションの修飾記述子
伝搬 列挙型:伝播· オプションのトランザクション伝播動作設定
隔離 列挙型:アイソレーション オプションのトランザクション分離レベル設定
読み取り専用 ブール値 読み取り/書き込みまたは読み取り専用トランザクション、デフォルトは読み取り/書き込み
タイムアウト 整数 トランザクションタイムアウト設定
ロールバック用 Class オブジェクトの配列。Throwable から継承する必要があります。 トランザクションのロールバックを引き起こした例外クラスの配列
クラス名のロールバック クラス名の配列。Throwable から継承する必要があります。 トランザクションのロールバックの原因となった例外クラス名の配列
noRollbackFor Class オブジェクトの配列。Throwable から継承する必要があります。 トランザクションのロールバックを引き起こさない例外クラスの配列
noRollbackForClassName クラス名の配列。Throwable から継承する必要があります。 トランザクションのロールバックを引き起こさない例外クラス名の配列

2、 propagation属性

propagation はトランザクションの伝播動作を表します。デフォルト値はPropagation.REQUIREDです。

属性 説明
伝播.必須 現在トランザクションが存在する場合はトランザクションに参加し、存在しない場合は新しいトランザクションを作成します (デフォルト)
伝播.サポート 現在のトランザクションがある場合はトランザクションに参加し、存在しない場合は非トランザクションで続行します。
伝播.必須 現在のトランザクションが存在する場合はトランザクションに参加し、存在しない場合は例外をスローします。
伝播.REQUIRES_NEW 新しいトランザクションを再作成します。現在のトランザクションがある場合、現在のトランザクションは暫定的なものです。
伝播.NOT_SUPPORTED 非トランザクション方式で実行します。現在のトランザクションがある場合、現在のトランザクションは暫定的なものです。
伝播.NEVER 非トランザクション方式で実行し、現在のトランザクションがある場合は例外をスローします。
Propagation.NESTED Propagation.REQUIRED と同じ効果

3. @Transactional アノテーションの使用シナリオは何ですか?

@Transactional アノテーションは、インターフェイス、クラス、クラス メソッドに適用できます。

  • class として使用される場合、このクラスのすべてのパブリック メソッドが同じトランザクション属性情報で構成されることを意味します。

  • メソッドとして使用する場合、クラスが @Transactional アノテーションで構成され、メソッドも @Transactional で構成されている場合、メソッドのトランザクションはクラスのトランザクション構成情報をオーバーライドします。

  • インターフェースとして使用する場合、インターフェースで @Transactional を使用し、CGLib 動的プロキシを使用するように Spring AOP を構成すると失敗するため、お勧めできません。

4. @Transactional アノテーションの失敗シナリオ?

  • @Transactional アノテーションは、非公開の変更メソッドに適用すると無効になります。

失敗の理由: Spring AOP プロキシでは、ターゲット メソッドの実行前後にTransactionInterceptor (トランザクション インターセプター)がインターセプトし、 DynamicAdvisedInterceptor (CglibAopProxy の内部クラス)のInterceptメソッドまたはJDKDynamicAopProxyinvokeメソッドが間接的にcomputeTransactionAttributeメソッドを呼び出します。 @Transactional アノテーションを取得するAbstractFallbackTransationAttributeSourceトランザクション構成情報。

1 protected TransactionAttribute computeTransactionAttribute(Method method,
2    Class<?> targetClass) {
3        // Don't allow no-public methods as required.
4        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
5        return null;
6    }


このメソッドは対象メソッドの修飾子がpublic であるかどうかをチェックし、非 public スコープでは@transactional の属性構成情報を取得しません。このうち、@Transactionalアノテーションはprotected メソッドと private Modified メソッドで使用されており、トランザクションは無効になりますが、エラーは報告されません。

  • @Transactional アノテーション属性伝播設定エラーによりアノテーションが失敗する

失敗の理由: 構成エラー。PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER の 3 つのトランザクション伝播方法はロールバックされません。

▪ 検証例: テスト用のデモを作成しました。デモの主な機能は次のとおりです。2 つのデータベース挿入操作を実行し、拡張情報フィールドにメモを追加します。

▪ 実行結果は次のとおりです。構築された注文番号が存在せず、注文クエリが空であるため、例外が発生します。データベースを観察すると、最初のデータベース挿入操作が正常に実行されたことがわかります。トランザクションのアノテーションが無効です

  • @Transactional アノテーション属性 rollbackFor が正しく設定されていないため、アノテーションが失敗します

rollbackFor では、トランザクションのロールバックをトリガーできる例外のタイプを指定できます。デフォルトでは、Spring は未チェック例外 (RuntimeException から継承) またはエラーをスローして、トランザクションをロールバックします。トランザクションで他のタイプの例外がスローされたが、Spring がトランザクションをロールバックすることが期待される場合は、 rollbackFor 属性を指定する必要があります。指定しないと失敗します。

  • 同じクラス内のメソッド呼び出しにより @Transactional が失敗する

たとえば、クラスデモにはメソッド A と B があり、メソッド B では @Transactional アノテーションが使用されています。メソッド A にはアノテーションがありませんが、デモ クラスはメソッド A を通じてメソッド B を呼び出します。このような間接的な呼び出しにより、@Transactionalメソッド B のトランザクション アノテーションが失敗する。

失敗の理由: 現在のクラス以外のコードでトランザクション メソッドが呼び出された場合にのみ、Spring によって生成されるプロキシ オブジェクト管理が存在します。(Spring AOP プロキシメカニズムが原因)。

検証例: デモの構築シーンは同じクラスにあり、テスト メソッドに @Transactional アノテーションを追加し、このアノテーションを querRiskScore メソッドに追加せず、querRiskScore メソッドでテスト メソッドを呼び出し、複数のメソッドが存在するかどうかを観察します。挿入操作は、例外によりロールバックが中断されました。


▪ 実行結果は次のとおりです。構築された注文番号が存在せず、注文クエリが空であるため、例外がトリガーされます。データベースを観察すると、最初のデータベース挿入操作は正常に実行されたことがわかりますが、2 番目のデータ挿入操作は実行されませでし。失敗し、例外によりトランザクション操作がトリガーされなかったため、検証 @Transactional アノテーション メソッド間の呼び出しは無効になります。

  • マルチスレッドタスクにより @Transaction ケースが失敗する可能性がある

失敗の原因: スレッドは Spring によって管理されていないため、スレッドはデフォルトでは Spring トランザクションを使用できず、Spring によって注入された Bean も取得できません。Spring の宣言型トランザクション管理によって管理されるメソッドではマルチスレッドが有効になっており、マルチスレッドのメソッドはトランザクションによって管理されません。

  • メソッド内の catch によって例外がキャッチされ、@Transactional が失敗します。

たとえば、メソッド B 内で例外がスローされ、このときメソッド A がメソッド B の例外を try-catch した場合、トランザクションは正常にロールバックできません。

失敗の理由: メソッド B では例外がスローされた後、現在のトランザクションをロールバックする必要があるためですが、メソッド A では例外を手動でキャプチャして処理するため、メソッド A は現在のトランザクションが正常にコミットされるべきであると考えます。org.springframework.transaction.UnexpectedRollbackException: ロールバック専用例外としてマークされているため、トランザクションはロールバックされます

▪インスタンスの検証: このシナリオの本質は、例外がキャッチされ、正常にスローできないため、@Transactional アノテーションが適切に動作しないことです。デモ インスタンス シナリオを簡略化し、次のようにシナリオを構築しました: @Transactional アノテーションを追加querRiskScore メソッドに送信し、その後 querRiskScore メソッドで例外をキャッチし、複数の挿入操作によって例外が原因でロールバックが中断されるかどうかを観察します。


▪ 実行結果は以下の通りです 構築された注文番号が存在せず、注文クエリが空で例外が発生しますが、メソッド内で例外を捕捉し、上位層にはスローしません 想定するシナリオデータ挿入の実行が 2 回失敗するということです。ただし、データベースを観察すると、最初のデータベース挿入操作は正常に実行さ2 番目のデータ挿入操作は正常に実行されたことがわかり、これは予想される結果と一致しません。 @Transactional アノテーションは、メソッド内で例外がキャッチされたシーンでは失敗します

理由: Spring のトランザクションはビジネスメソッドが呼び出される前に開始され、ビジネスメソッドの実行後にコミットまたはロールバックが実行されます。トランザクションが実行されるかどうかは、実行時例外がスローされるかどうかによって決まります。実行時例外がスローされ、ビジネス メソッド キャッチがない場合、トランザクションはロールバックされます。

3.「ビジネス」知識の復習

1. トランザクションとは何ですか?

トランザクション(Transaction)とは、システム内のデータにアクセスしてデータを更新する一連の操作で構成されるプログラム実行の論理単位(Unit)です。

通常、トランザクションとはデータベース トランザクションを指しますが、データベース トランザクションを使用すると、次の 2 つの利点があります。

  • 複数のアプリケーションがデータベースに同時にアクセスする場合、トランザクションはこれらのアプリケーション間の分離方法を提供し、互いの操作が相互に干渉するのを防ぐことができます。

  • トランザクションは、一連のデータベース操作を障害から通常の状態に回復する方法を提供すると同時に、データベースが異常な状態でもデータの一貫性を維持する方法を提供します。

2. トランザクションの特徴は何ですか?

原子性、一貫性、分離性、永続性。トランザクションの ACID 特性と呼ばれます。

  • 原子性

トランザクションのアトミック性とは、トランザクションがアトミックな操作シーケンス単位である必要があることを意味します。つまり、トランザクションに含まれる各操作は、1 回の実行中に 2 つの状態のみ表示されます。すべてが正常に実行され、何も実行されません。いずれかの操作が失敗すると、トランザクション全体が失敗し、実行された他の操作は取り消されてロールバックされます。すべての操作が成功した場合にのみ、トランザクション全体が正常に完了したとみなされます。

  • 一貫性

トランザクションの一貫性とは、トランザクションの実行によってデータベース データの整合性と一貫性が破壊されないことを意味し、トランザクションの実行前と実行後、データベースは一貫性のある状態にある必要があります。つまり、トランザクション実行の結果は、データベースをある一貫した状態から別の一貫した状態に遷移させる必要があるため、データベースに成功したトランザクション コミットの結果のみが含まれている場合、そのデータベースは一貫した状態にあると言われます。また、データベース システムの運用中に障害が発生した場合、一部のトランザクションは完了する前に強制的に中断され、これらの未完了のトランザクションによってデータベースに加えられた変更の一部が物理データベースに書き込まれ、データベースが誤った状態になります。 . 、または矛盾した状態。

  • 隔離

トランザクションの分離とは、同時実行環境において、同時実行トランザクションが互いに分離され、トランザクションの実行が他のトランザクションによって干渉されないことを意味します。つまり、異なるトランザクションが同じデータを同時に操作する場合、各トランザクションは独自の完全なデータ空間を持ちます。つまり、トランザクション内で使用される操作とデータは他の同時トランザクションから分離されており、同時に実行されるトランザクション間のトランザクションは干渉できません。お互いに。

  • 持続性

トランザクションがコミットされると、その変更はデータベースに永続的に保存され、データベースに障害が発生した場合でも影響はありません。トランザクションの耐久性は 100% 持続することはできず、トランザクション自体の観点からのみ保証されます。また、ハードディスクの損傷などの外部の理由によりデータベースに障害が発生すると、送信されたすべてのデータが機能しなくなる可能性があることに注意してください。紛失してしまう。

3. Spring のトランザクションとは何ですか?

Spring は優れたトランザクション管理メカニズムも提供しており、主にプログラムによるトランザクション宣言的なトランザクションに分かれています。

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

コード内でトランザクションの送信、ロールバック、その他の操作を手動で管理することを指し、コードは比較的煩わしいものです。プログラムによるトランザクション方法では、開発者はコード内でオープン、コミット、ロールバックなどのトランザクションを手動で管理する必要があります。

public void test() {

      TransactionDefinition def = new DefaultTransactionDefinition();

      TransactionStatus status = transactionManager.getTransaction(def);

      try {

         // 事务操作

         // 事务提交

         transactionManager.commit(status);

      } catch (DataAccessException e) {

         // 事务提交

         transactionManager.rollback(status);

         throw e;

      }

}


  • 宣言的トランザクション

宣言型トランザクションは、特定のビジネスとトランザクション処理を分離する AOP に基づいたアスペクト指向であり、コードの侵入性が非常に低いため、実際の開発でよく使用されます。TX や AOP の XML 設定ファイル方式と @Transactional アノテーション方式がよく使われます。

▪宣言的トランザクションの利点:

コードへの侵入はなく、メソッド内にビジネス ロジックを記述するだけで済むため、コードの量が大幅に節約されます。

▪宣言的トランザクションの欠点:

1. 宣言的トランザクションの粒度:宣言的トランザクションの制限は、メソッドに最小粒度を適用する必要があることです。これは、長期にわたる同時実行性の高いシナリオには適していません。

2. 宣言型トランザクションは開発者によって見落とされやすいため、トランザクション ネスト方式で RPC リモート呼び出し、MQ 送信、Redis 更新、ファイル書き込み、その他の操作がある場合、次のシナリオが存在する可能性があります。

▪ トランザクション ネストの方法では、RPC 呼び出しは成功しますが、ローカル トランザクション ロールバックにより、RPC 呼び出しはロールバックに失敗します (分散トランザクションについてはまだ説明されていません)。

▪トランザクション ネスト方式でのリモート呼び出しはトランザクション サイクル全体を延長し、その結果トランザクションのデータベース接続が一貫して占有され、同様の操作が多すぎるとデータベース接続プールが枯渇します。

3. 宣言型トランザクションを誤って使用すると、一部のシナリオで障害が発生します。

4. まとめ

著者: Jingdong Technology Song Huichao

出典: JD Cloud 開発者コミュニティ

RustDesk 1.2: Flutterを使用してデスクトップバージョンを書き換え、Waylandの GPT-4モデルアーキテクチャリーク疑惑をサポート: 混合エキスパートモデルを使用して1.8兆個のパラメータが含まれている(MoE) マスク氏は、 V23の適合に成功したWSL CentOSプロジェクトの主張に基づい てxAI会社の設立を発表した すべてに公開」Rust 1.71.0 安定版リリースReact Angular.js の瞬間はありますか? Microsoft、CalibriMicrosoft に代わる新しいデフォルト フォント Aptos を発表: Windows 11 でRust IntelliJ IDEA 2023.1.4 リリースを使用する取り組みを強化
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10089156