23

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

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

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

コンテナ管理のトランザクションを使用しているので、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);
       }
    }

}

...ここで、 EJB3によってラップされないように、チェックされAppValidationExceptionた例外またはチェックされていない例外に注釈が付けられます。@ApplicationException

時々、私はとで早期の制約違反を引き起こし、EntityManager.flush()それを捕まえることができますが、常にではありません。それでも、コミット時に遅延制約チェックによってスローされたデータベースレベルの制約違反をトラップできるようにしたいと思っています。これらの違反は、JTAがコミットしたときにのみ発生します

ヘルプ?


すでに試しました:

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

見る:

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

javax.validation.ValidationExceptionJDKの例外です。折り返しを防ぐために注釈を追加する@ApplicationExceptionように変更することはできません。注釈を追加するためにサブクラス化することはできません。私のコードではなく、EclpiseLinkによってスローされます。マークを付けると、@ApplicationExceptionArjuna(AS7のJTA impl)がRollbackExceptionとにかくそれをラップするのを止めることができるかどうかはわかりません。

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

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

...しかし、インターセプターはJTAで発砲するようです(これは賢明で通常は望ましいことです)ので、私がキャッチしたい例外はまだスローされていません。

私が望んでいるのは、 JTAが処理を実行した後に適用される例外フィルターを定義できるようにすることだと思います。何か案は?


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


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

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

4

4 に答える 4

7

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

コンテナの責任のEJB3.1仕様、セクション13.6.1Bean-ManagedTransactionDemarcationを参照してください。それは読みます:

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

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

つまり、トランザクションを効果的に開始およびコミットする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管理トランザクションに関するJavaEE6チュートリアルページでは、これについて、またはBMTの呼び出し方法については何も言及されていません。

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


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

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

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

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

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

これらのエラーが発生する場合は、UIフレームワークレベルの例外フィルターを追加しました。EJBExceptionJTAのRollbackExceptionラッピングのPersistenceExceptionラッピングとEclipseLink固有の例外のラッピングのラッピングを確認しPSQLException、SQLStateを調べて、それに基づいて決定を下します。ばかげたラウンドアバウトですが、機能します

于 2012-08-06T04:26:30.190 に答える
5

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

@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);
    }    
}

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

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

于 2012-08-03T01:01:27.410 に答える
2

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

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

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

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

于 2012-08-06T04:51:32.110 に答える
2

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

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

于 2013-10-24T14:25:42.893 に答える