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 終了です。