埋め込み EJB コンテナ

(この記事は2010年5月19日に投稿したものの再掲です)

今回は「埋め込みEJBコンテナ」を使ってみようと思います。

埋め込みEJBコンテナは、Java SEのプログラムからEJBコンテナを呼び出し、EJBを操作できてしまうものです。一旦EJBコンテナを呼び出すわけですから、JNDIによるデータソースのlookupなどもJava SE上からテストできます。

EJB 3.0以降、EJBは特定のアノテーションを付加したJavaクラスとなったため、コンテナの外側であればJUnitでテスト可能でした。今回の埋め込みEJBコンテナを使用すると、コンテナの内側のEJBをJUnitでテストできるようになります。

埋め込みEJBコンテナ全般については、Oracle(Sun)の寺田さんの次のエントリに詳しいので、まずはそちらを参照してください。

http://yoshio3.com/2010/04/07/ejb-3-1-%E3%81%AE%E6%96%B0%E6%A9%9F%E8%83%BD%E6%A6%82%E8%A6%81/

この記事では、実際に筆者が埋め込みEJBを試したときに真っ先にはまったところを重点的に解説します。

まず、今回のサンプルで使用したEJB(Stateless Bean)を示します。

package jp.jupitoris.javasample.embed;

import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.sql.DataSource;

@Stateless
public class DatabaseObject {
  @Resource(name = "jdbc/oracle")
  private DataSource ds;
	
  public Date getSysDate() {
    try {
      Date sysdate = null;
			
      Connection conn = ds.getConnection();
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery(
          "SELECT sysdate FROM dual");
      if (rs.next())
        sysdate = rs.getDate(1);
      stmt.close();
      conn.close();
      
      return sysdate;
    } catch (SQLException e) {
      // DB が必須のシステムでチェックすべき例外
      // を投げても仕方がないから
      throw new RuntimeException(e);
    }
  }
}

上記の例では、@Resourceアノテーションを使用してJNDI lookupを行っています。

次に、EJBを埋め込むための実行可能なJavaクラスを示します。

package jp.jupitoris.javasample.embed;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;
import javax.naming.NamingException;

public class EmbeddedSample {
  public static void main(String[] args) {
    Map param = new HashMap();
    param.put("org.glassfish.ejb.embedded.glassfish.instance.root", 
        // GlassFish v3 ドメインのディレクトリパス名
        "/glassfishv3/glassfish/domains/domain1");
    EJBContainer container = EJBContainer.createEJBContainer(param);

    try {
      DatabaseObject dbo = (DatabaseObject) container.getContext().lookup(
          // java:global/クラスフォルダ名/クラス名
          "java:global/bin/DatabaseObject");
      System.out.println(dbo.getSysDate());
    } catch (NamingException e) {
      e.printStackTrace();
    }
  }
}

EmbeddedSampleクラスを実行するには、クラスパス上に次の2個のライブラリが必要です。

  • javax.ejb.jar (/glassfishv3/glassfish/modules/)
  • glassfish-embedded-static-shell.jar (/glassfishv3/glassfish/lib/embedded/)

 これらをGlassFishのインストールディレクトリからコピーして、クラスパスを通せばOKです。

埋め込みEJBコンテナのインスタンスを取得するには、EJBContainerクラスのファクトリ・メソッドcreateEJBContainerを使用します。このとき、パラメータにGlassFishのドメインのディレクトリ名を指定する必要があるので、コードは次のようになります。

Map param = new HashMap();
param.put("org.glassfish.ejb.embedded.glassfish.instance.root",
        // GlassFish v3 ドメインのディレクトリパス名
        "/glassfishv3/glassfish/domains/domain1");
EJBContainer container = EJBContainer.createEJBContainer(param);

パラメータのキーは "org.glassfish.ejb.embedded.glassfish.instance.root" という文字列です。値のディレクトリパス名は、各環境に合わせた値を設定してください。

コンテナにあるEJBのインスタンスを取得するには、次のようになります。

DatabaseObject dbo = (DatabaseObject) container.getContext().lookup(
        // java:global/クラスフォルダ名/クラス名
        "java:global/bin/DatabaseObject");

lookup()に渡すJNDI名はグローバルJNDIと呼ばれる書式で、Java EE 6 から採用されました。このグローバルJNDIに沿って、.class ファイルが存在する場所を表します。この例ではjava:globalの後ろのbinがクラス・フォルダ(EclipseのJavaプロジェクト作成時のデフォルト)、DatabaseObjectがクラス名です。ここも実行環境によって設定を調整します。

上記の例を実行すると、JNDI lookupによりデータソースが取得され、その後のJDBCを使った処理が行われます。これでアプリケーションサーバーを起動してWAR/EARをデプロイしなくても、EJBやJNDIのテスト/デバッグができるようになりました。