web-dev-qa-db-ja.com

container-managed-tx EJBがコミットしたときにJTAによってスローされた例外をキャッチしてラップするにはどうすればよいですか?

重要なデータモデルを管理するEJB3クラスの問題に苦労しています。コンテナ管理のトランザクションメソッドがコミットすると、制約検証の例外がスローされます。それらがEJBExceptionでラップされないようにしたいのですが、代わりに、呼び出し元が処理できる正常なアプリケーション例外をスローします。

適切なアプリケーション例外でラップするには、それをキャッチできる必要があります。ほとんどの場合、検証例外は私が行ったEntityManager呼び出しからスローされるため、単純なtry/catchがその役割を果たします。

残念ながら、一部の制約はコミット時にのみチェックされます。たとえば、マップされたコレクションでの@Size(min=1)の違反は、コンテナ管理のトランザクションがコミットされたときにのみキャッチされ、トランザクションメソッドの最後で制御を離れます。検証が失敗したときにスローされた例外をキャッチしてラップできないため、コンテナーはそれを_javax.transaction.RollbackException_でラップし、thatを呪われたEJBExceptionでラップします。呼び出し元は、すべてのEJBExceptionsをキャッチし、原因チェーンに飛び込んで、それが検証の問題であるかどうかを確認する必要があります。これは、本当にニースではありません。

コンテナ管理のトランザクションを使用しているので、EJBは次のようになります。

_@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {

    @Inject private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterest() throws AppValidationException {
       try {
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
       } catch (ValidationException ex) {
           // Won't catch violations of @Size on collections or other
           // commit-time only validation exceptions
           throw new AppValidationException(ex);
       }
    }

}
_

...ここで、AppValidationExceptionは、チェックされた例外またはチェックされていない例外であり、_@ApplicationException_の注釈が付けられているため、EJB3によってラップされません。

EntityManager.flush()を使用して早期の制約違反をトリガーし、それをキャッチできる場合がありますが、常にそうとは限りません。それでも、本当にコミット時に遅延制約チェックによってスローされたデータベースレベルの制約違反をトラップできるようにしたいのですが、それらはeverは、JTAがコミットしたときに発生します。

助けて?


すでに試しました:

Bean管理トランザクションは、制御するコード内でコミットをトリガーできるようにすることで、問題を解決します。残念ながら、Bean管理のトランザクションは_TransactionAttributeType.REQUIRES_NEW_に相当するものを提供しないため、これらはオプションではありません。BMTを使用してトランザクションを一時停止する方法はありません。 JTAの厄介な見落としの1つ。

見る:

...ただし、警告と詳細については回答を参照してください。

_javax.validation.ValidationException_はJDKの例外です。ラップを防ぐために_@ApplicationException_アノテーションを追加するように変更することはできません。注釈を追加するためにサブクラス化することはできません。それは私のコードではなく、EclpiseLinkによってスローされます。 _@ApplicationException_とマークすると、Arjuna(AS7のJTA impl)がRollbackExceptionでラップするのを止めることができるかどうかはわかりません。

EJB3インターセプターを次のように使用しようとしました:

_@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}
_

...しかし、インターセプターはinsideJTA(これは賢明で通常は望ましい)を起動するように見えるので、キャッチしたい例外はまだスローされていません。

私が欲しいのは、適用される例外フィルターを定義できることだと思いますafter JTAがその役割を果たします。何か案は?


私はJBossAS7.1.1.FinalとEclipseLink2.4.0を使用しています。 EclipseLinkは これらの手順 に従ってJBossモジュールとしてインストールされますが、当面の問題ではそれほど重要ではありません。


PDATE:この問題についてさらに検討した結果、JSR330検証の例外に加えて、DBから SQLIntegrityConstraintViolationException をトラップできる必要があることに気付きました。 それぞれSQLSTATE 40P01および40001でのデッドロックまたはシリアル化の失敗のロールバック 。そのため、コミットがスローされないようにするだけのアプローチはうまく機能しません。チェックされたアプリケーション例外は、JTAインターフェースが自然に宣言しないため、JTAコミットを介してスローすることはできませんが、チェックされていない_@ApplicationException_アノテーション付き例外はスローできるはずです。

アプリケーションの例外を便利にキャッチできるところならどこでも、あまりきれいではありませんが、EJBExceptionをキャッチして、その内部でJTA例外と、基になる検証またはJDBC例外を調べ、それに基づいて意思決定を行うことができるようです。 JTAの例外フィルター機能がなければ、おそらくそうしなければならないでしょう。

22
Craig Ringer

私はこれを試していません。しかし、私はこれがうまくいくはずだと推測しています。

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
class TheEJB {

    @Inject
    private TheEJB self;

    @Inject private EntityManager em;

    public methodOfInterest() throws AppValidationException {
       try {
           self.methodOfInterestImpl();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterestImpl() throws AppValidationException {
        someEntity.getCollectionWithMinSize1().removeAll();
        em.merge(someEntity);
    }    
}

コンテナは新しいトランザクションを開始し、コミットすることが期待されていますwithinmethodOfInterest、したがって、ラッパーメソッドで例外をキャッチできるはずです。

追伸:答えは@LairdNelsonによって提供されたエレガントなアイデアに基づいて更新されます...

5
Hasan Ceylan

元の質問で_REQUIRES_NEW_とBMTについて私が言ったことには注意が必要です。

コンテナの責任のEJB3.1仕様のセクション13.6.1Bean-Managed Transaction Demarcationを参照してください。それは読む:

コンテナは、次のようにBean管理のトランザクション境界を使用してエンタープライズBeanインスタンスへのクライアント呼び出しを管理する必要があります。クライアントがエンタープライズBeanのクライアントビューの1つを介してビジネスメソッドを呼び出すと、コンテナはクライアント要求に関連付けられている可能性のあるすべてのトランザクションを一時停止しますインスタンスに関連付けられたトランザクションがある場合(ステートフルセッションBeanインスタンスが以前のビジネスメソッドでトランザクションを開始した場合に発生します)、コンテナはメソッドの実行をこのトランザクションに関連付けます。 Beanインスタンスに関連付けられたインターセプターメソッドがある場合、これらのアクションはインターセプターメソッドが呼び出される前に実行されます。

(イタリック鉱山)。これは、BMT EJBが、コンテナ管理txが関連付けられている呼び出し元のJTAtxを継承しないことを意味するため重要です。現在のtxはsuspendedであるため、BMT EJBがtxを作成する場合、それはnewトランザクションであり、コミットすると、トランザクションのみがコミットされます。

つまり、トランザクションを開始してコミットするBMT EJBメソッドを、事実上_REQUIRES_NEW_であるかのように使用して、次のようにすることができます。

_@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {

    @Inject private EntityManager em;

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry
    // point to this method; it's effectively `REQUIRES_NEW`.
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException {
       try {
           tx.begin();
           // For demonstration's sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
           tx.commit();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       } catch (PersistenceException ex) {
           // Go grubbing in the exception for useful nested exceptions
           if (isConstraintViolation(ex)) {
               throw new AppValidationException(ex);
           } else {
               throw new SomeOtherAppException(ex);
           }
       }
    }

}
_

これにより、コミットが私の制御下に置かれます。複数の異なるEJBにまたがる複数の呼び出しにまたがるトランザクションが必要ない場合、これにより、コミット時のエラーを含むすべてのエラーをコード内で処理できます。

Bean管理トランザクションに関するJava EE 6チュートリアルページ は、これや、BMTの呼び出し方法については何も言及していません。

私がリンクしたブログでBMTが_REQUIRES_NEW_をシミュレートできないという話は有効ですが、はっきりしていません。 Bean管理のトランザクションEJBがある場合、別のトランザクションを開始するためにトランザクションを一時停止することはできません開始した。別のヘルパーEJBを呼び出すと、txが一時停止され、_REQUIRES_NEW_と同等のものが得られる場合がありますが、まだテストしていません。


問題の残りの半分(複数の異なるEJBおよびEJBメソッド間で実行する必要がある作業があるためにコンテナー管理トランザクションが必要な場合)は、防御コーディングによって解決されます。

エンティティマネージャの早期の熱心なフラッシュにより、JSR330検証ルールが検出できる検証エラーをキャッチできるため、DBからチェック制約や整合性違反エラーが発生しないように、それらが完全で包括的であることを確認する必要があります。コミット時間。私はそれらをきれいに扱うことができないので、私は本当に防御的にそれらを避ける必要があります。

その防御的なコーディングの一部は次のとおりです。

  • エンティティフィールドでの_javax.validation_アノテーションの多用、およびそれだけでは不十分な場合の_@AssertTrue_検証メソッドの使用。
  • コレクションの検証制約がサポートされています。たとえば、Aのコレクションを持つエンティティBがあります。 Aには少なくとも1つBが必要なので、@Size(min=1)制約をBのコレクションに追加しました。 A
  • カスタムJSR330バリデーター。オーストラリアのビジネス番号(ABN)などのカスタムバリデーターをいくつか追加して、無効なバリデーターをデータベースに送信してDBレベルの検証エラーをトリガーしないようにしました。
  • EntityManager.flush()によるエンティティマネージャーの早期フラッシュ。これにより、JTAがトランザクションをコミットするときではなく、コードの制御下にあるときに検証が強制的に実行されます。
  • JSR330検証で検出できない状況が発生してコミットが失敗しないようにするための、EJBファサード内のエンティティ固有の防御コードとロジック。
  • 実用的な場合は、_REQUIRES_NEW_メソッドを使用して早期コミットを強制し、EJB内の障害を処理したり、適切に再試行したりできるようにします。これには、ビジネスメソッドの自己呼び出しに関する問題を回避するためのヘルパーEJBが必要になる場合があります。

SwingでJDBCを直接使用していたときのように、シリアル化の失敗やデッドロックを適切に処理して再試行することはできません。そのため、コンテナーからのこのすべての「ヘルプ」により、一部の領域で数ステップ後退しました。ただし、他の場所ではhugeの厄介なコードとロジックを節約できます。

これらのエラーが発生する場合は、UIフレームワークレベルの例外フィルターを追加しました。 EJBExceptionがJTAをラップしているのを見てRollbackExceptionPersistenceExceptionをラップしているのを見てEclipseLink固有の例外がPSQLExceptionをラップしているのを見て、SQLStateを調べ、それに基づいて決定を下しますそれ。それはばかげた回り道ですが、それは機能します

6
Craig Ringer

javax.validation.ValidationExceptionはJDK例外です。折り返しを防ぐために@ApplicationExceptionアノテーションを追加するように変更することはできません

答えに加えて、XML記述子を使用して、サードパーティのクラスにApplicationExceptionとして注釈を付けることができます。

2
jjmontes

設定 eclipselink.exception-handlerExceptionHandler の実装を指すプロパティは有望に見えましたが、うまくいきませんでした。

ExceptionHandlerのJavaDocは...悪い...なので、 テスト実装 とテスト( 1 、-)を確認する必要があります。 2 )それを使用します。もう少し便利なドキュメントがあります ここ

例外フィルターを使用して、他のすべてに影響を与えずにいくつかの特定のケースを処理することは難しいようです。 PSQLExceptionをトラップし、SQLSTATE 23514(CHECK制約違反)をチェックし、そのための有用な例外をスローし、それ以外は何も変更しないようにしたかったのです。それは実用的ではないようです。

結局、私はアイデアを捨てて、可能な場合はBean管理のトランザクション(現在はそれらがどのように機能するかを正しく理解している)と、JTAコンテナー管理のトランザクションを使用するときに不要な例外を防ぐための防御的なアプローチを採用しました。

2
Craig Ringer