デベロッパー2006年7月4日 10:00
文字サイズ文字サイズ小文字サイズ中文字サイズ大

EJB 2.1からEJB 3.0への移行

この記事のURLhttp://japan.internet.com/developer/20060704/26.html
著者:Deepak Vohra
海外internet.com発の記事

はじめに

 EJB 2.1からEJB 3.0への移行方法を説明する前に、この移行によるメリットを紹介しなければなりません。基本的に、EJB 3.0ではEJBの作成に必要なクラス、インターフェイス、および配備記述子の数が削減されています。EJB 3.0仕様では、Beanの抽象クラスの代わりに従来の普通のJavaオブジェクト(Plain Old Java Object:POJO)を使い、コンポーネントインターフェイスとホームインターフェイスの代わりに従来の普通のJavaインターフェイス(Plain Old Java Interfaces:POJI)を使うことによってEJB開発を簡易化しています。ただし、インターフェイスの指定は任意なので、必ずしも含める必要はありません。

 EJB 3.0での変更点は次のとおりです。

  • EJB名を指定する配備記述子「ejb-jar.xml」、Beanクラス名、インターフェイス、finderメソッド、コンテナ管理リレーションシップ(Container-Managed Relationship:CMR)、およびベンダ固有の配備記述子は必要なくなりました。これらの代わりに、Beanクラス内のメタデータアノテーションを使います。アノテーションが利用できるようになったのはJDK 5.0からなので、EJB 3.0仕様のEJBを開発する場合にはJDK 5.0が必要です。
  • EJB 3.0では、EJB 2.1のcreate()メソッドとfinderメソッドの代わりに、javax.persistence.EntityManager APIを使います。EJB 2.1では、クライアントアプリケーションはエンティティBeanオブジェクトとセッションBeanオブジェクトの参照を取得する場合にJNDI名を使いますが、EJB 3.0では@Resourceアノテーション、@Injectアノテーション、および@EJBアノテーションを使います。
  • EJB 2.1では、エンティティBeanとセッションBeanの開発にjavax.ejbパッケージのクラスとインターフェイスを使い、セッションBeanはSessionBeanインターフェイスを実装し、エンティティBeanはEntityBeanインターフェイスを実装していました。一方、EJB 3.0では、セッションBeanクラスとエンティティBeanクラスはPOJOで、SessionBeanインターフェイスとEntityBeanインターフェイスは実装しません。
  • EJB 2.1では、セッションBeanクラスは1つ以上のejbCreateメソッド、コールバックメソッド、setSessionContextメソッド、およびビジネスメソッドを指定します。同様に、エンティティEJBは、ejbCreate()メソッド、ejbPostCreate()メソッド、コールバックメソッド、コンテナ管理パーシスタンス(Container-Managed Persistence:CMP)、getter CMR、setter CMR、getterメソッド、setterメソッド、およびビジネスメソッドを指定します。EJB 3.0では、セッションBeanクラスが指定するのはビジネスメソッドのみです。同様に、エンティティBeanが指定するのは、ビジネスメソッド、各種Beanプロパティのgetter/setterメソッド、およびBeanリレーションシップのgetter/setterメソッドのみです。
  • EJB 2.1では、ホームインターフェイスはjavax.ejb.EJBHomeインターフェイスを拡張し、ローカルホームインターフェイスはjavax.ejb.EJBLocalHomeインターフェイスを拡張します。リモートインターフェイスはjavax.ejb.EJBObjectインターフェイスを拡張し、ローカルインターフェイスはjavax.ejb.EJBLocalObjectインターフェイスを拡張します。EJB 3.0では、ローカルインターフェイスとホームインターフェイスを指定しません。代わりにPOJIのビジネスインターフェイスを使います。セッションBeanクラスでビジネスインターフェイスが指定されないと、EJBサーバーはセッションBeanクラスからPOJIのビジネスインターフェイスを生成します。

 これらの変更点を念頭に置いて、本稿ではサンプルセッションBeanとサンプルエンティティBeanをEJB 2.1からEJB 3.0に移行する場合に知っておかなければならないことを中心に詳しく説明します。

セッションBeanを移行する

 EJB 2.1のサンプルセッションBeanクラスであるBookCatalogBeanには、ejbCreateメソッド、getTitle()ビジネスメソッド、および次のコールバックメソッドが定義されています。

// BookCatalogBean.java
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class BookCatalogBean implements SessionBean
{
   private SessionContext ctx;
   public String getEdition(String title)
   {
      if(title.equals("Java & XML"))
         return new String("2nd edition");
      if(title.equals("Java and XSLT"))
         return new String("1st edition");
   }
   public void ejbCreate(){}
   public void ejbRemove() {}
   public void ejbActivate() {}
   public void ejbPassivate() {}
   public void setSessionContext(SessionContext ctx)
     {this.ctx=ctx;}
}

 EJB 3.0では、セッションBeanはメタデータのアノテーションを使ってBeanの種類を指定します。Beanがステートフルの場合は@Statefulアノテーション、ステートレスの場合は@Statelessアノテーションを使います。また、セッションBeanでは、コンポーネントインターフェイスとホームインターフェイスの代わりにビジネスインターフェイスを使います。ビジネスインターフェイスはPOJIで、アノテーション@Local@Remoteを使って、ローカルまたはリモートのいずれかを指定することができます。また、セッションBeanでは、ローカルインターフェイスとリモートインターフェイスの両方を実装することもできます。

 Beanクラスでインターフェイス(ローカルまたはリモート)を指定しないと、既定ではEJBサーバーによって自動的にローカルビジネスインターフェイスが生成されます。また、@Localアノテーションと@Remoteアノテーションを使ってインターフェイスクラスを指定することもできます。

 次に示すEJB 3.0のセッションBeanは、「BookCatalogBean.java」という名前のPOJOで、前述のEJB 2.1のステートレスセッションBeanを移行したものです。@Statelessアノテーションを使い、ローカルビジネスインターフェイスを実装し、@Localアノテーションでローカルインターフェイスのクラス名を指定していることに注意してください。

// BookCatalogBean.java EJB 3.0 Session Bean
@Stateless
@Local ({BookCatalogLocal.java})
public class BookCatalogBean implements
   BookCatalogLocal
{
   public String getEdition(String title)
   {
      if(title.equals("Java & XML"))
         return new String("2nd edition");
      if(title.equals("Java and XSLT"))
         return new String("1st edition");
   }
}

 また、このEJB 3.0のセッションBeanでは、EJB 2.1バージョンのコンポーネントインターフェイスとホームインターフェイスの代わりに、@Localアノテーションを使ってアノテーションを付けてローカルビジネスインターフェイス(POJI)を使っています。

EJBセッションBeanのクライアントを移行する

 EJB 2.1では、セッションBeanのクライアントはJNDI名を使ってセッションBeanオブジェクトを取得します。次に示すクライアントは、JNDI名「BookCatalogLocalHome」を使ってローカルホームオブジェクトを取得し、create()メソッドを呼び出します。その後、クライアントは、getEdition(String)ビジネスメソッドを使って指定されたタイトルの版の値を出力します。

import javax.naming.InitialContext;
public class BookCatalogClient
{

   public static void main(String[] argv)
   {
      try{
         InitialContext ctx=new InitialContext();
         Object objref=ctx.lookup(
            "BookCatalogLocalHome");
         BookCatalogLocalHome catalogLocalHome =
            (BookCatalogLocalHome)objref;
         BookCatalogLocal catalogLocal =
            (BookCatalogLocal) catalogLocalHome.
            create();
         String title="Java and XML";
         String edition =
            catalogLocal.getEdition(title);
         System.out.println("Edition for Title: "
            + title + " " + edition);

      }
      catch(Exception e){}
   }
}

 EJB 3.0では、@Injectアノテーション、@Resourceアノテーション、または@EJBアノテーションを使って実装する依存性注入によって、セッションBeanオブジェクトの参照を取得します。次に示すEJB 3.0のセッションBeanのクライアントクラスは、@Injectアノテーションを使ってBookCatalogBeanクラスを注入します。タイトルの版の値の取得には、これまでどおりgetEdition(String)ビジネスメソッドを使います。

public class BookCatalogClient
{
   @Inject BookCatalogBean;
   BookCatalogBean catalogBean;

   String title="Java and XML";
   String edition=catalogBean.getEdition(edition);
   System.out.println("Edition for Title: "
      + title + " " + edition);
}

エンティティBeanを移行する

 次に、EJB 2.1のエンティティBeanをEJB 3.0仕様に移行する方法を説明します。EJB 2.1のエンティティBeanは、EntityBeanインターフェイスを実装し、getter CMPフィールドメソッド、setter CMPフィールドメソッド、getter CMRフィールドメソッド、setter CMRフィールドメソッド、コールバックメソッド、およびejbCreate/ejbPostCreateメソッドから成り立ちます。サンプルエンティティBeanである「BookCatalogBean.java」(リスト1)は、CMPフィールドのtitleauthor、およびpublisherと、CMRフィールドのeditionsから成り立ちます。

リスト1 BookCatalogBean.java
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;

public class BookCatalogBean implements EntityBean
{
   private EntityContext ctx;
   public abstract void setTitle();
   public abstract String getTitle();
   public abstract void setAuthor();
   public abstract String getAuthor();
   public abstract void setPublisher();
   public abstract String getPublisher();
   public abstract void setEditions(
      java.util.Collection editions);
   public abstract java.util.Collection getEditions();

   public String ejbCreate(String title)
   {
      setTitle(title);
      return null;
   }

   public void ejbRemove() {}
   public void ejbActivate() {}
   public void ejbPassivate() {}
   public void ejbLoad() {}

   public void ejbStore() {}

   public void setEntityContext(EntityContext ctx)
   {
      this.ctx=ctx;
   }

   public void unsetEntityContext()
   {
      ctx = null;
   }
}

 このEJB 2.1のエンティティBeanの「ejb-jar.xml」配備記述子(リスト2)では、EJBのクラスとインターフェイス、CMPフィールド、EJB QLクエリ、およびCMRリレーションシップを指定します。BookCatalogBeanエンティティBeanでは、finderメソッドのfindByTitle()とCMRフィールドのeditionsを定義します。

リスト2 「ejb-jar.xml」配備記述子
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC
  "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
  "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
  <enterprise-beans>
    <entity>
    <ejb-name>BookCatalog</ejb-name>
    <local-home>BookCatalogLocalHome</local-home>
    <local>BookCatalogLocal</local>
    <ejb-class>BookCatalogBean</ejb-class>
    <persistence-type>Container</persistence-type>
    <prim-key-class>String</prim-key-class>
    <reentrant>False</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>BookCatalog</abstract-schema-name>
      <cmp-field>
          <field-name>title</field-name>
      </cmp-field>
      <cmp-field>
          <field-name>author</field-name>
      </cmp-field>
      <cmp-field>
          <field-name>publisher</field-name>
      </cmp-field>
          <query>
         <query-method>
           <method-name>findByTitle</method-name>
           <method-params>
            <method-param>java.lang.String</method-param>
           </method-params>
         </query-method>
         <ejb-ql>
           <![CDATA[SELECT DISTINCT OBJECT(obj)
             FROM BookCatalog obj WHERE obj.title = ?1 ]]>
         </ejb-ql>
       </query>
      </entity>
  </enterprise-beans>
  <relationships>
    <ejb-relation>
      <ejb-relation-name>BookCatalog-Editions</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          BookCatalog-Has-Editions
        </ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
        <relationship-role-source>
          <ejb-name>BookCatalog</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>editions</cmr-field-name>
            <cmr-field-type>java.util.Collection</cmr-field-type>
        </cmr-field>
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>
          Editions-Belong-To-BookCatalog
        </ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
               <cascade-delete />
        <relationship-role-source>
          <ejb-name>Edition</ejb-name>
        </relationship-role-source>
      </ejb-relationship-role>
    </ejb-relation>
  </relationships>
</ejb-jar>

 一方、EJB 2.1のBeanクラスに対応するEJB 3.0のエンティティBeanクラスはPOJOで、かなり簡潔化されています(リスト3)。

リスト3 BookCatalogBean.java
import javax.persistence.Entity;
import javax.persistence.NamedQuery;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.OneToMany;

@Entity
@NamedQuery(name="findByTitle", queryString =
   "SELECT DISTINCT OBJECT(obj) FROM BookCatalog obj WHERE
   obj.title = ?1")

public class BookCatalogBean
{
   public BookCatalogBean(){}
   public BookCatalogBean(String title)
   {
      this.title=title;
   }

   private String title;
   private String author;
   private String publisher;

   @Id
   @Column(name="title", primaryKey="true")

   public String getTitle(){return title;}
   public void setTitle(){this.title=title;}

   public void setAuthor(String author){this.author=author;}
   public String getAuthor(){return author;}

   public void setPublisher(String publisher)
   {
      this.publisher=publisher;
   }
   public String getPublisher(){return publisher;}
   private java.util.Collection<Edition> editions;

   @OneToMany
   public void setEditions(
      java.util.Collection editions)
   {
      this.editions=editions;
   }

   public  java.util.Collection getEditions(){return editions;}

}

 このBeanクラスのEJB 3.0バージョンでは、メタデータアノテーションの@Entityを使います。finderメソッドは、EJB 2.1では配備記述子「ejb-jar.xml」ファイルで要素を使って指定しますが、EJB 3.0では@NamedQueriesアノテーションと@NamedQueryアノテーションを使って指定します。CMRリレーションシップは、EJB 2.1では「ejb-jar.xml」ファイルで要素を使って指定しますが、EJB 3.0ではメタデータアノテーションを使って指定します。主キーフィールドは、アノテーション@Idを使って指定します。表1に、EJB 3.0のよく使われるメタデータアノテーションと、アノテーションの影響を受ける要素(該当する場合)を示します。

表1 EJB 3.0のメタデータアノテーションの一部
アノテーション説明アノテーションの要素
@EntityエンティティBeanクラスのアノテーションを付けます。
@TableエンティティBeanテーブルのアノテーションを付けます。@Tableを指定しないと、既定でテーブル名はEJB名になります。name、schema
@Id主キープロパティまたはフィールドのアノテーションを付けます。
@Transient非永続プロパティまたはフィールドのアノテーションを付けます。
@ColumnエンティティBeanの永続プロパティのマップ列にアノテーションを付けます。name、primaryKey、nullable、length。既定ではプロパティ名またはフィールド名です。
@NamedQueries名前付きクエリのグループにアノテーションを付けます。
@NamedQuery名前付きクエリまたはfinderメソッドに関連付けられたクエリにアノテーションを付けます。name、queryString
@OneToMany1対多の関連付けにアノテーションを付けます。Cascade
@OneToOne1対1の関連付けにアノテーションを付けます。Cascade
@ManyToMany多対多の関連付けにアノテーションを付けます。Cascade
@ManyToOne多対1の関連付けにアノテーションを付けます。Cascade

 EJB 3.0では、EJB 2.1のエンティティBeanクラスのfinderメソッドfindByTitle()に対応するアノテーションとして、@NamedQueryアノテーションを使います。また、EJB 2.1のエンティティBeanのCMRリレーションシップBookCatalog-Editionsに対応するアノテーションとして、EJB 3.0では@OneToManyアノテーションを使います。@Idアノテーションで、IDプロパティtitleにアノテーションを付けます。@Columnアノテーションで、IDプロパティtitleに対応するデータベース列を指定します。@Columnアノテーションを使ってエンティティBeanの永続プロパティにアノテーションが付けられていない場合、EJBサーバーは、列名はエンティティBeanのプロパティ名と同じであると見なします。エンティティBeanの非永続プロパティには、@Transientアノテーションを使ってアノテーションを付けます。

EJBエンティティBeanのクライアントを移行する

 EJB 2.1では、エンティティBeanのホームオブジェクトまたはローカルホームオブジェクトは、エンティティBeanのホームインターフェイスまたはローカルホームインターフェイスのcreate()メソッドを使って作成します。また、EJB 2.1では、エンティティBeanのクライアントは、JNDI検索を使ってエンティティBeanのローカルオブジェクトまたはリモートオブジェクトを取得します。次に、EJB 2.1でエンティティBeanのローカルホームオブジェクトを作成するサンプルコードを示します。

InitialContext ctx=new InitialContext();
Object objref=ctx.lookup("BookCatalogLocalHome");
BookCatalogLocalHome catalogLocalHome =
   (BookCatalogLocalHome)objref;

 このコードでは、BookCatalogLocalHomeBookCatalogBeanエンティティBeanのJNDI名です。

 EJB 2.1では、参照を取得した後、クライアントはcreate()メソッドを使ってローカルオブジェクトを作成します。

BookCatalogLocal catalogLocal = (BookCatalogLocal)
   catalogLocalHome.create(title);

 EJB 2.1では、finderメソッドを使ってローカルホームオブジェクトまたはホームオブジェクトからローカルオブジェクトまたはリモートオブジェクトを取得します。例えば、次のようにfindByPrimaryKeyメソッドを使ってローカルオブジェクトを取得することができます。

BookCatalogLocal catalogLocal = (BookCatalogLocal)
   catalogLocalHome.findByPrimaryKey(title);

 EJB 2.1では、remove()メソッドを使ってエンティティBeanのインスタンスを削除します。

catalogLocal.remove();

 EJB 3.0では、永続性の実装、検索、および削除にはjavax.persistence.EntityManagerクラスを使います。表2に、EJB 2.1のメソッドに対応する、EntityManagerクラスのよく使われるメソッドを示します。

表2 EntityManagerクラスのメソッド
EntityManagerのメソッド説明
persist(Object entity)エンティティBeanのインスタンスを永続化します。
createNamedQuery(String name)Queryオブジェクトのインスタンスを作成して名前付きクエリを実行します。
find(Class entityClass, Object primaryKey)エンティティBeanのインスタンスを検索します。
createQuery(String ejbQl)Queryオブジェクトを作成してEJBQLクエリを実行します。
remove(Object entity)エンティティBeanのインスタンスを削除します。

 EJB 3.0のエンティティBeanのクライアントクラスでは、@Resourceアノテーションを使ってEntityManagerオブジェクトを注入します。

@Resource
private EntityManager em;

 エンティティBeanのインスタンスを永続化する場合は、次のように、EntityManager.persist()メソッドを呼び出します。

BookCatalogBean catalogBean = new
   BookCatalogBean (title);
em.persist(catalogBean);

 同様に、エンティティBeanのインスタンスを取得する場合は、EntityManager.find()メソッドを呼び出します。

BookCatalogBean catalogBean = (BookCatalogBean)
   em.find("BookCatalogBean", title);

 EJB 3.0では、名前付きクエリfindByTitleに対応するクライアントクラスのfinderメソッドを定義することができます(EJB 2.1のfinderメソッドとは異なります)。このfinderメソッド内で、createNamedQuery(String)メソッドを使ってQueryオブジェクトを取得します。

Query query=em.createNamedQuery("findByTitle");

 Queryオブジェクトのパラメータは、setParameter(int paramPosition, String paramValue)メソッドまたはsetParameter(String parameterName, String value)メソッドを使って設定します。パラメータの位置はゼロベースです。

query.setParameter(0,  title);

 BookCatalogBeanオブジェクトのコレクションを取得するには、Query.getResultList()メソッドを使います。クエリの結果が1つだけの場合は、代わりにgetSingleResult()メソッドを使います。

java.util.Collection catalogBeanCollection =
   (BookCatalogBean)query.getResultList();

 最後に、EntityManager.remove(Object entity)メソッドを使ってエンティティBeanのインスタンスを削除します。

BookCatalogBean catalogBean;
em.remove(catalogBean);

 リスト4に、EJB 3.0のエンティティBeanに対する完全なステートレスセッションBeanのクライアントクラスを示します。

リスト4 BookCatalogClientクラス(完全なEJB 3.0クライアントクラス)
import javax.ejb.Stateless;
import javax.ejb.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;

@Stateless
@Local
public class BookCatalogClient implements BookCatalogLocal
{


   @Resource
   private EntityManager em;

   public void create(String title)
   {
      BookCatalogBean catalogBean=new BookCatalogBean(title);
      em.persist(catalogBean);
   }
   public BookCatalogBean findByPrimaryKey(String title)
   {
      return (BookCatalogBean)em.find("BookCatalogBean", title);
   }

   public java.util.Collection findByTitle(String title)
   {
      Query query=em.createNamedQuery("findByTitle");
      query.setParameter(0, title);
      return (BookCatalogBean)query.getResultList();
   }

   public void remove(BookCatalogBean catalogBean)
   {
      em.remove(catalogBean);
   }
}

 本稿では、サンプルセッションBeanとサンプルエンティティBeanをEJB 2.1からEJB 3.0に移行する場合に必要なサンプルコードの変更について説明しました。EJB 2.0から移行する場合も同様です。

 本稿の執筆時では、JBoss Application ServerOracle Application ServerCaucho Application Serverなど、EJB 3.0仕様を既にサポートしているアプリケーションサーバーがいくつかありました。残念ながら、実装はアプリケーションサーバーによって異なっているようです。必ずしもEJB 3.0の機能がすべて実装されているわけではありません。詳しくは、アプリケーションサーバーの資料でご確認ください。

著者紹介

Deepak Vohra(Deepak Vohra)
O’Reillyの技術レビュー担当者として『WebLogic: The Definitive Guide』を担当。NuBeanコンサルタント、Web開発者でもある。Sun認定資格(Java 1.4プログラマおよびWebコンポーネントディベロッパJ2EEバージョン)を取得。メールの宛先はdvohra09@yahoo.com。

Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/