EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その5 ApplicationException

今回はApplicationExceptionアノテーションです。「Simplified API」ドキュメントで言うと10.9節です。

ApplicationExceptionアノテーションは例外に適用されます。このアノテーションは例外の種類がアプリケーション例外でありクライアントに直接(たとえばラップされないまま)伝播されることを示すそうです。ApplicationExceptionアノテーションはchecked例外にもunchecked例外にも適用されます。rolllback要素は、例外がスローされたときにコンテナにトランザクションロールバックさせるかどうかを示すためにつかわれるそうです。

アノテーションの定義です。rolllbackのデフォルトはfalseなのですね。

@Target(Type) @Retention(RUNTIME)
public @interface ApplicationException {
  boolean rollback() default false
}

uncheckedな例外にもApplicationExceptionアノテーションがつけられるというのが意外でした。「Application Exception」 nealy equal「checked Exception」と思っていたのですがそういう考え方は駄目ですね。
EJB Core Contracts and Requirements」をちらりと見てました。Application Exceptionはchecked例外だったりunchecked例外だったりするけどjava.rmi.RemoteExceptionとそのサブクラスではないのだとありました。ちなみにApplication Exceptionの反対はSystem Exceptionです。java.rmi.RemoteExceptionとそのサブクラス、それにApplication Exceptionではないchecked例外がSystem Exceptionだそうです。
手元のEJB 2.x の本を見る限りApplication Exceptionの定義がEJB 3.0のドキュメントと違うので、EJB 3.0でApplication Exceptionの定義が変わってunchecked例外を含むようになったみたいに見える。


実験コーナー〜。アノテーションをつけた場合とつけない場合でchecked例外とunchecked例外がそれぞれどのようにクライアントに返るのか、トランザクションはどうなるかを確認してみます。
とりあえず例外を4つ作りました。

public class MyException extends Exception {
}
public class MyRuntimeException extends RuntimeException {
}
@ApplicationException
public class MyAppException extends MyException{
}
@ApplicationException
public class MyAppRuntimeException extends MyRuntimeException {
}


LogicとDaoをつくりました。LogicでDaoの更新メソッドを実行したあと例外をスローします。

public interface EmployeeLogic {
  void insert(int no, String name) throws MyException;
}
@Stateless
@Remote(EmployeeLogic.class)
public class EmployeeLogicBean implements EmployeeLogic {

  @EJB
  private EmployeeDao dao;

  @Resource
  private SessionContext ctx;

  public void insert(int no, String name) throws MyException {
    dao.insert(no, name);
    throw new MyAppException(); // ここで4種類の例外をそれぞれ投げてみる
  }
}
public interface EmployeeDao {
  void insert(int no, String name);
}
@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) {
      }
    }
  }
}


クライアントです。

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(1008, "ゴン");
  }


デプロイしてクライアントを動かします。クライアントに送出された例外とトランザクションの結果をまとめます。(EJB 2.1だと送出される例外はトランザクションスコープやトランザクション属性によって変わるはずなので、3.0 もそうだと思われる。ということはいつもこのとおりになるわけではないとおもいます)

  • MyExceptionをthrowした場合
    • MyExceptionがクライアントに送出されました。トランザクションはコミットされます。RemoteExceptionが送出されると思ったんだけど...。ローカル呼び出しに最適化されているのかな?
  • MyRuntimeExceptionをthrowした場合
    • javax.ejb.EJBExceptionがクライアントに送出されました。トランザクションロールバックされます。EJBExceptionにMyRuntimeExceptionがラップされるのかと思っていたのですが、それはそうではなくて、getCausedByExceptionメソッドの戻り値はnullでした。unchecked例外はラップされないらしい。調べてみたらこれは仕様どおりのよう。
  • MyAppExceptionをthrowした場合
  • MyAppRuntimeExceptionをthrowした場合
    • MyAppRuntimeExceptionがクライアントに送出されました。トランザクションはコミットされます。


ということで、ApplicationExceptionアノテーションが導入されたことで次の3つのことがEJB 3.0で可能になったといえます。


おっ、今日はうまくまとめられたかも。Chapter 10 その5終了です。