JBoss EJB 3.0と拡張機能

  トランザクション
はじめに

トランザクションサポートは、どのエンタープライズミドルウェアでも鍵となる機能です。それは不安定なネットワークやハードウェア障害においてさえ、システムの保全性を保証します。トランザクションはデータベースアプリケーションで特に重要です。アプリケーションのビジネスロジックでは、連続したデータベースの更新はすべて成功するか、さもなくばまったく更新が起こらず再度更新をやり直すことになる、ということがよく求められます。EJB 3.0ではアノテーションを使うことで、どのPOJOクラスのどのメソッドにもトランザクションプロパティを非常に簡単に宣言できます。事実上、トランザクションはEJB 3.0のエンティティBeanとセッションBeanを結び付けます。このトレイルでは、トランザクションアノテーションの使い方を学んでいきます。

トランザクションアノテーション

EJB 3.0アプリケーションでは、トランザクションプロパティはセッションBeanのメソッドに対して最も頻繁に宣言されます。あるメソッドがトランザクションを要求する場合、そのメソッドでのデータベースの更新を含むすべての操作は、メソッドが正常に終了した後にのみコミットされます。もしこのメソッド内で(あるいはこのメソッドが呼ぶメソッド内で)キャッチされないアプリケーション例外が投げられるならば、トランザクションマネージャはすべての変更(すなわちデータベースの更新)をロールバックします。トランザクションを宣言するアノテーションは@TransactionAttributeです。これは次の引数を取ることができます。

  • REQUIRED: アノテーションの付いたメソッドはトランザクションの内部で実行されます。呼び出し側のメソッドが既にトランザクション内にいる場合は、そのトランザクションが使われます。そうでなければ、新しいトランザクションが生成されます。
  • MANDATORY: アノテーションの付いたメソッドはトランザクションの内部で呼び出されなければなりません(すなわち呼び出し側は既にトランザクションを持っていなければなりません)。そうでなければ、エラーが投げられます。
  • REQUIRESNEW: アノテーションの付いたメソッドは新たに生成されたトランザクションの内部で実行されます。呼び出し側のメソッドが既にトランザクション内にいる場合、そのトランザクションは中断されます。
  • SUPPORTS: アノテーションの付いたメソッドがトランザクション内から呼び出された場合、そのトランザクションが使われます。トランザクションなしに呼び出された場合、トランザクションは生成されません。
  • NOT_SUPPORTED: アノテーションの付いたメソッドがトランザクション内から呼び出された場合、エラーが投げられます。

メソッドがトランザクションアノテーションを持たない場合、デフォルトのトランザクションプロパティであるREQUIREDがEJB 3.0コンテナによって割り当てられます。

EJB 3.0では、EntityManagerはデータベースの保全性を確保するためトランザクションコンテキスト内で動作しなければなりません。コンテナは、通常はデータベースがコミットするときに同期します(デフォルトでは、データベースはカレントスレッドの最後に同期化されることを思い出してください)。でも、それに先立ってDBクエリーが実行されるときはその前にも同期します。トランザクションの途中にインメモリの更新をデータベースへフラッシュするため、EntityManager.flush()を呼ぶことができます。

トランザクションロールバック

トランザクションはアプリケーションがRuntimeExceptionApplicationExceptionを投げたときに失敗します。RuntimeExceptionは、通常はデータベースに関するものです。次は独自のApplicationExceptionを定義する例です。このApplicationExceptionをトランザクション内のどこで投げても、トランザクションを失敗させることができます--たとえ現実にデータベースがエラーを起こしていなくても。


@ApplicationException(rollback=true)
public class TransException extends Exception {

  public TransException () { }

}

トランザクションが失敗したときには、たとえ永続コンテキストがトランザクション中にフラッシュされていても、データベースはトランザクション前の状態にロールバックされます。すべての管理対象エンティティBeanオブジェクトは切り離されます。失敗したトランザクションからこれらのエンティティBeanを再利用したい場合、これらのIDを手動で0に設定しなければなりません。

トランザクションの使用例

次の例では、updateExchangeRate() メソッドでのエンティティBean更新ループ内でランダムにアプリケーション例外を生成しています。このメソッドはトランザクションを宣言されているので、この例外によりすべての更新は失敗します。


@Stateless
public class TransCalculator implements Calculator {

  @PersistenceContext
  protected EntityManager em;


  // ... ...

  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public void updateExchangeRate (double newrate) throws Exception {
    Collection <TimedRecord> rc = 
      em.createQuery("from TimedRecord r").getResultList();
    int size = rc.size ();
    
    for (Iterator iter = rc.iterator(); iter.hasNext();) {
      TimedRecord r = (TimedRecord) iter.next();
      r.setSaving(r.getSaving() * newrate);
      r.setResult(r.getResult() * newrate);

      // Emulate a failure
      
      // Calculate failure probability for each loop
      // in order for the overall failure probability
      // to be 50%
      double prob = Math.pow (0.5, 1./size);
      if (Math.random() > prob) {
        // Emulated failure causes rollback
        throw new TransException ();
        
        // Or throw a RuntimeException to trigger rollback
      }
    }
  }

次のボタンをクリックして新しい為替更新アプリケーションを起動してください。為替の更新を数回行ってみると、すべてのレコードが更新されているか、ひとつのレコードも更新されないかの、どちらかであることが分かるでしょう。たとえ更新ループの途中で例外が投げられても、レコードの一部だけが更新されるようなことはありません。この例外は半分ほど更新したところで投げられます。

ソースコード参照

セッションBean

クライアント

まとめ

トランザクションマネージャはEJB 3.0コンテナによって提供されるとても重要なサービスです。それはアプリケーションのどのPOJOにも宣言的に適用されますが、たいていはセッションBeanに適用されます。標準的なEJB 3.0のトランザクションはスレッドベースです(すなわちカレントスレッドまたはメソッド呼び出しの最後に、コミットまたはロールバックします)。次のトレイルでは、スレッドベースのトランザクションマネージャを越え、複数スレッドにまたがるアプリケーショントランザクションについて論じます。