EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その4 Timeout

今回はTimeoutアノテーションです。「Simplified API」ドキュメントで言えば10.8節です。

どのように説明されているかというと、TimeoutアノテーションエンタープライズBeanのtimeoutメソッドを示すために使用します。ってこれだけしか書いてない…。
定義も

@Target({METHOD}) @Retention(RUNTIME)
public @interface Timeout{}

と、いたってシンプル。
これだけじゃぜんぜん使い方がわからなかいんですけど...

でも、JBossのサンプル見たらなんとなくわかりました。Timeoutアノテーションはコールバックされるメソッドにつけるアノテーションなんですね。それと、Timer関係の仕組みってEJB 2.1からあるんですね。いままで一度も使ったことありませんでした。一般的には使われている機能なんでしょうか?

JBossのサンプルを参考にTimeoutアノテーションを使って何か動くものを作ってみました(JBossのサンプルとほぼ同じという話も)。
ビジネスインタフェース

public interface Scheduler {
  void schedule(long duration);
}

Beanクラス:Timeoutアノテーションつかってます。

@Stateless
@Remote(Scheduler.class)
public class SchedulerBean implements Scheduler {
  
  @Resource
  private SessionContext ctx; 
  
  public void schedule(long duration) {
    ctx.getTimerService().createTimer(duration, "Hello!");
  }
  
  @Timeout
  public void timeout(Timer t) {
    System.out.println(t.getInfo());
    t.cancel();
  }
}

クライアント

public class SchedulerClient {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    Scheduler scheduler = (Scheduler)ctx.lookup(Scheduler.class.getName());
    scheduler.schedule(10000);
  }
}

デプロイしてクライアントを動かすと10秒くらいたってJBossコンソールに「Hello!」と表示されました。とりあえずOK!

chapter 10 その4 終わり。

EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その3 TransactionAttribute

今回はTransactionAttributeアノテーションを扱います。Simplified APIのドキュメントで言えば10.5節です。

TransactionAttributeアノテーションはコンテナがビジネスメソッドをトランザクションコンテキスト内で呼び出すかどうかを指定するそうです。トランザクションアトリビュートのセマンティクスは「EJB Core Contracts and Requirements」ドキュメントのchapter 12 に定義されているらしいです。
TransactionAttributeアノテーションはコンテナ管理のトランザクション境界が使われる場合にのみ使うことができるそうです。このアノテーションはBeanクラスもしくはビジネスメソッドに指定できて、クラスに指定した場合はビジネスインタフェースのメソッドすべてに適用され、メソッドに指定した場合はそのメソッドのみに適用されるそうです。クラスとメソッド両方に指定があった場合はメソッドのアノテーションが優先です。
Bean管理のトランザクション境界が使われていてTransactionAttributeアノテーションが指定されていない場合、REQUIREDのトランザクション属性があるとみなされるそうです。

TransactionAttributeTypeとTransactionAttributeの定義を写してみます(写経?)。

public enum TransactionAttributeType {
  MANDATORY,
  REQUIRED,
  REQUIRES_NEW,
  SUPPORTS,
  NOT_SUPPORTED,
  NEVER  
}
@Target ({METHOD, TYPE}) @Retention(RUNTIME)
public @interface TransactionAttribute {
  TransactionAttributeType value()
    default TransactionAttributeType.REQUIRED;
}


ではTransactionAttributeを使った簡単なコードを動かしてみます。今回はREQUIREDとREQUIRES_NEWを使って監査ログをとるといったようなサンプルを作ってみたいと思います。監査ログはREQUIRES_NEWでとるべしといった話がJ2EE勉強会であったと思います。

  • 登場人物はClient、EmployeeLogic(Bean)、EmployeeDao(Bean)、AuditLogic(Bean)、AuditDao(Bean)
  • 使用するテーブルはEMPとAUDIT
  • EmployeeLogicBeanクラスのトランザクション属性は指定しない(DefaultのREQUIREDを使う)
  • AuditLogicBeanクラスのメソッドのトランザクション属性をREQUIRES_NEWに指定。
  • EmployeeLogicBeanのAroundInvokeでAuditLogicを呼び出す。
  • EmployeeLogicBeanとAuditLogicBeanが異なったトランザクションで実行されていることを確かめるためEmployeeLogicBeanでトランザクションロールバックさせてみる。

結果として次のようになるはず

  • EMPテーブルのデータは追加されないがAUDITテーブルにはデータが追加される


EMPテーブルとAUDITテーブル:監査ログって普通どんな風にとるもんなんでしょう?とりあえずAUDITテーブルは、呼び出されたクラスの名称、メソッド名、パラメータを記録できるようにしました。

CREATE TABLE EMP (
   EMPNO NUMERIC(4) NOT NULL PRIMARY KEY,
   ENAME VARCHAR(10)
)
CREATE TABLE AUDIT (
   CLASSNAME VARCHAR(50),
   METHODNAME VARCHAR(50),
   PARAMETERS VARCHAR(100)
)


EmployeeLogic ビジネスインタフェース

public interface EmployeeLogic {
  void insert(int no, String name);
}

EmployeeLogic Beanクラス:TransactionAttributeアノテーションは使っていない(トランザクション属性はdefaultのREQUIRED)。AroundInvokeメソッドでAuditLogicを呼び出している。SessionContextのsetRollbackOnly()を呼び出している。

@Stateless
@Remote(EmployeeLogic.class)
public class EmployeeLogicBean implements EmployeeLogic {

  @EJB
  private AuditLogic auditLogic;

  @EJB
  private EmployeeDao dao;

  @Resource
  private SessionContext ctx;

  public void insert(int no, String name) {
    dao.insert(no, name);
    ctx.setRollbackOnly(); // 意図的にロールバック
  }

  @AroundInvoke
  public Object audit(InvocationContext inv) throws Exception {
    auditLogic.audit(inv.getBean().getClass(), inv.getMethod(), inv
        .getParameters());
    return inv.proceed();
  }
}


AuditLogic ビジネスインタフェース

public interface AuditLogic {
  void audit(Class clazz, Method method, Object[] parameters);
}

AuditLogic Beanクラス:メソッドにTransactionAttributeアノテーションを指定している。TypeはREQUIRES_NEW。

@Stateless
public class AuditLogicBean implements AuditLogic {
  
  @EJB
  private AuditDao dao;
  
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void audit(Class clazz, Method method, Object[] parameters) {
    StringBuilder b = new StringBuilder();
    for(Object o : parameters) {
      b.append(o.toString());
      b.append(", ");
    }
    b.setLength(b.length() - 2);
    dao.insert(clazz.getName(), method.getName(), b.toString());
  }
}


AuditDao ビジネスインタフェース

public interface AuditDao {
  void insert(String className, String methodName, String parameters);
}

AuditDao Beanクラス

@Stateless
public class AuditDaoBean implements AuditDao {

  @Resource(name = "DefaultDS")
  private DataSource dataSource;

  public void insert(String className, String methodName, String parameters) {
    Connection con = null;
    try {
      con = dataSource.getConnection();
      PreparedStatement ps = con
          .prepareStatement("INSERT INTO audit VALUES(?, ?, ?)");
      ps.setString(1, className);
      ps.setString(2, methodName);
      ps.setString(3, parameters);
      ps.executeUpdate();
    } catch (SQLException e) {
      throw new RuntimeException(e);
    } finally {
      try {
        con.close();
      } catch (SQLException e) {
      }
    }
  }
}


EmployeeDaoのビジネスインタフェースとBeanクラス、それとEmployeeLogicを呼び出すクライアントは前回(Chapter 10 その2)と同じ。

デプロイしてクライアントを実行させると期待通りEMPテーブルは更新されずAUDITテーブルだけ更新されました。
AUDITテーブルのデータ

 CLASSNAME                   METHODNAME PARAMETERS 
 --------------------------- ---------- ---------- 
 study.ejb.EmployeeLogicBean insert     1001, ゴン 


Chapter10 その3 終了です。

EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その2 TransactionManagement

地震長かった...。入門記どころじゃないんですけどTransactionManagementアノテーションです。

TransactionManagementアノテーションはセッションBeanやメッセージ駆動型Beanのトランザクション境界のタイプを指定するそうです。タイプにはコンテナ管理とBean管理の2種類があって、セッションBeanやメッセージ駆動型BeanにTransactionManagementアノテーションが指定されていない場合、そのBeanはコンテナ管理のトランザクション境界をもつとみなされます、とあります。
defaultはコンテナ管理ということで、いままで動かしてきたセッションBeanやメッセージ駆動型Beanはどれも自動でトランザクションに入っていたんですね。

TransactionManagementアノテーションとTransactionManagementのタイプの定義を写してみます(写経?)。

@Target(TYPE) @Retention(RUNTIME)
public @interface TransactionManagement {
  TransactionManagementType value()
    default TransactionManagementType.CONTAINER;
}
public enum TransactionManagementType {
  CONTAINER,
  BEAN
}


実験コーナー〜。Bean管理のトランザクションを使って、SQLの更新がコミットされているかどうか見てみたいと思います。。まずJBossにくっついているHSQLDBにテーブルをつくります。JBossの管理コンソールからHSQLDBGUIツールを動かすにはhsqldb-ds.xmlの設定を変える必要みたい?。hsqldb-ds.xmlの設定についてはhttp://cnd.daitec.co.jp/openSo/documents/jboss/GettingStartedV4/dukesbank.htmlを参考にさせてもらいました。

HSQLDBGUIツールでEMPテーブルを作ります。

CREATE TABLE EMP (
   EMPNO NUMERIC(4) NOT NULL PRIMARY KEY,
   ENAME VARCHAR(10)
)


DAOを作ります。
DAOのビジネスインタフェース

public interface EmployeeDao {
  void insert(int no, String name);
}

DAOのBeanクラス:たんにinsertをしたいだけです。

@Stateless
public class EmployeeDaoBean implements EmployeeDao {

  @Resource(name = "DefaultDS")
  private DataSource dataSource;

  public void insert(int no, String name) {
    Connection con = null;
    try {
      con = dataSource.getConnection();
      PreparedStatement ps = con
          .prepareStatement("INSERT INTO emp VALUES(?, ?)");
      ps.setInt(1, no);
      ps.setString(2, name);
      ps.executeUpdate();
    } catch (SQLException e) {
      throw new RuntimeException(e);
    } finally {
      try {
        con.close();
      } catch (SQLException e) {
      }
    }
  }
}


Logicを作ります。
Logicのビジネスインタフェース

public interface EmployeeLogic {
  void insert(int no, String name);
}

LogicのBeanクラス:TransactionManagementアノテーションつかってTransactionManagementTypeにBEANを指定してます。UserTransactionを使ってBean内でトランザクションの開始とコミットを行います。

@Stateless
@Remote(EmployeeLogic.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class EmployeeLogicBean implements EmployeeLogic {

  @EJB
  private EmployeeDao dao;

  @Resource
  private UserTransaction tx;
  
  public void insert(int no, String name) {
    try {
      tx.begin();
      dao.insert(no, name);
      tx.commit();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}


クライアント:Logicのビジネスインタフェースに値を渡してます。

public class EmployeeClient {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    EmployeeLogic logic = (EmployeeLogic)ctx.lookup(EmployeeLogic.class.getName());
    logic.insert(1001, "ゴン");
  }
}

デプロイしてクライアントを実行し、HSQLDBGUIでEMPテーブルを見てみるとデータが追加されていることがわかりました。
今回は無理やりBean管理のトランザクションを使ってみました。コンテナ管理を使った場合、LogicのBeanクラスはこんな感じになると思います。

@Stateless
@Remote(EmployeeLogic.class)
public class EmployeeLogicBean implements EmployeeLogic {

  @EJB
  private EmployeeDao dao;

  public void insert(int no, String name) {
    dao.insert(no, name);
  }
}

コンテナ管理では当然UserTransactionを使う必要はないですが、UserTransactionのDIの記述を残したままデプロイしたらJBossに怒られて「it is illegal to inject UserTransaction into a CMT bean」とか言われました。

EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その1 前置き

今回からChapter 10 Metadata Annotationsです。
Chapter 10では「Simplified API」で導入されたメターデータアノテーションについて説明しています。これらのアノテーションは基本的にjavax.ejbパッケージ内にあるそうです。永続化に関するアノテーションは「Java Persistence API」ドキュメントで定義されているそうです。

ざっとみると全部で30個近くアノテーションがあるようです。すべてを見ていっても細かすぎるような気がするので、いままで入門記で扱っていなかったり説明を読んで新しい発見があったりするものだけを選んで進めていこうかなぁ。

いまのところ入門記で扱ってみたいアノテーションは次のとおりです。

  • TransactionManagement
  • TransactionAttribute
  • Timeout
  • ApplicationException
  • Security関連
    • RolesReferenced
    • RolesAllowed
    • PermitAll
    • DenyAll
    • RunAs
    • SecurityRoles

Securityのところが理解できるか心配。とりあえず chapter 10 その1 を終了します。