Sun Java System Application Server Enterprise Edition 8.2 アップグレードと移行

第 4 章 EJB 1.1 から EJB 2.0 への移行

Sun Java System Application Server 8.2 では EJB 1.1 仕様も引き続きサポートされますが、拡張機能を活用できるように EJB 2.0 アーキテクチャーを使用することをお勧めします。

EJB 1.1 から EJB 2.0 に移行するには多少の変更が必要ですが、そのうちのいくつかはコンポーネントのソースコード内の変更です。

必要となる変更は、基本的に EJB 1.1 と EJB 2.0 の違いに関連しています。これらすべてについて、次のトピックで説明します。

EJB QL (EJB Query Language)

EJB 1.1 仕様では、個々のアプリケーションサーバーに対する検索メソッド用のクエリーを形成および表現するための方法や言語が残されています。多くのアプリケーションサーバーベンダーでは、開発者が SQL を使用してクエリーを作成することを可能にしていますが、特定のアプリケーションサーバー製品に固有の独自言語を使用している場合もあります。このようにクエリーの実装が混在しているため、アプリケーションサーバー間の不整合が生じています。

EJB 2.0 仕様では、こうした不整合や欠点の多くを修正するために、EJB Query Language、つまり EJB QL と呼ばれるクエリー言語が導入されています。EJB QL は SQL92 に基づいています。EJB QL は、特にコンテナ管理による持続性を備えたエンティティー Bean に対して、検索メソッドと選択メソッドの両方の形式で、クエリーメソッドを定義します。EJB QL が SQL より優れている主な点は、EJB コンテナにまたがる移植性と、エンティティー Bean の関係をナビゲートする機能です。

ローカルインタフェース

EJB 1.1 アーキテクチャーでは、セッションおよびエンティティー Beans に 1 つのインタフェースのタイプ、つまりリモートインタフェースが備わっています。このインタフェースを通じて、クライアントやその他のアプリケーションコンポーネントからのアクセスが可能になります。リモートインタフェースは、Bean インスタンスがリモート機能を持つように設計されています。Bean は RMI を継承しており、ネットワークを通じて分散クライアントと対話します。

EJB 2.0 では、セッション Bean とエンティティー Bean は、クライアントに対して、リモートインタフェースローカルインタフェースという 2 種類のインタフェースを通して、メソッドを公開することができます。2.0 のリモートインタフェースは、1.1 で使用されているリモートインタフェースと同じものです。したがって、Bean は RMI を継承しており、ネットワーク層全体にメソッドを公開し、分散クライアントとの対話機能も同じです。

ただし、セッションおよびエンティティー Bean のローカルインタフェースは、ローカルクライアントである (つまり同じ EJB コンテナ内に共存している) EJB からの軽量アクセスをサポートしています。さらに EJB 2.0 仕様では、ローカルインタフェースを使用する EJB が同じアプリケーション内に存在する必要があります。つまり、ローカルインタフェースを使用するアプリケーションの EJB の配備記述子を、1 つの ejb-jar ファイル内に格納する必要があります。

ローカルインタフェースは標準の Java インタフェースです。これは RMI を継承していません。エンタープライズ Bean は、ローカルインタフェースを使用して、同じコンテナ内に存在するその他の Bean に対してメソッドを公開します。ローカルインタフェースを使用することで、Bean はもっと緊密にクライアントと連携し、リモートメソッド呼び出しによるオーバーヘッドなしで直接アクセスすることが可能になります。

さらに、ローカルインタフェースでは、参照セマンティクスを使用して Bean 間で値を渡すことが許可されます。オブジェクトそのものではなく、オブジェクトへの参照を渡すようになるので、大量のデータを含むオブジェクトを渡す際に発生するオーバーヘッドが軽減され、パフォーマンスが向上します。

EJB 2.0 のコンテナ管理による持続性 (CMP)

EJB 2.0 仕様では CMP が強化され、複数のエンティティー Bean 間で関係を持たせることが可能になりました。これは、コンテナ管理による関係 (CMR) と呼ばれます。コンテナによって、関係および関係の参照整合性を管理します。

EJB 1.1 仕様で提供されていた CMP モデルにはより多くの制限がありました。EJB 1.1 アーキテクチャーにおける CMP は、データベースやリソースマネージャーのタイプに依存しないデータアクセスに制限されていました。リモートインタフェースを通して公開できるのは、エンティティー Bean のインスタンス状態のみで、Bean の関係を公開する方法はありませんでした。EJB 1.1 バージョンの CMP は、エンティティー Bean クラスのインスタンス変数をデータベースまたはリソースマネージャー内でこれらの状態を表すデータ項目へマッピングすることに依存しています。CMP のインスタンスフィールドは配備記述子内で指定されます。Bean が配備されると、デプロイヤはツールを使用して、インスタンスフィールドのデータ項目に対するマッピングを実装するコードを生成します。

Bean の実装クラスのコーディング方法を変更する必要もあります。EJB 2.0 仕様では、CMP を使用するエンティティー Bean の実装クラスは、抽象クラスとして定義されるようになります。

この節では、次の項目について説明します。

持続フィールドの定義

EJB 2.0 仕様では、エンティティー Bean のインスタンス変数を CMP フィールドまたは CMR フィールドとして指定することが可能です。これらのフィールドは配備記述子内に定義します。CMP フィールドは cmp-field という要素でマークされ、コンテナ管理による関係フィールドは cmr-field という要素でマークされます。

実装クラスでは、CMP および CMR フィールドを public 変数として宣言しないように注意してください。その代わりに、これらの CMP および CMR フィールドの値を取得および設定するには、エンティティー Bean 内に get および set メソッドを定義します。この意味で、2.0 CMP を使用する Bean は JavaBeans モデルに従っていると言えます。クライアントは、インスタンス変数に直接アクセスするのではなく、エンティティー Bean の get および set メソッドを使用して、これらのインスタンス変数を取得および設定しているからです。get および set メソッドが関連するのは、CMP または CMR フィールドに指定された変数のみであることに注意してください。

エンティティー Bean の関係の定義

前述のとおり、EJB 1.1 アーキテクチャーはエンティティー Bean 間の CMR をサポートしていません。EJB 2.0 アーキテクチャーでは、1 対 1 と 1 対多の両方の CMR がサポートされています。これらの関係は CMR フィールドによって表され、これらのフィールドは配備記述子内で関係としてマークされます。配備記述子内の CMR フィールドは、各アプリケーションサーバーに適した配備ツールを使用して設定します。

CMP フィールドと同じように、Bean では CMR フィールドをインスタンス変数として宣言しません。その代わり、Bean ではこれらのフィールドに対して get および set メソッドが提供されています。

メッセージ駆動型 Bean

メッセージ駆動型 Bean は、EJB 2.0 アーキテクチャーによって導入されたもう 1 つの新機能です。メッセージ駆動型 Bean は、JMS (Java Message Service) を通して配信された非同期メッセージを処理するトランザクション対応のコンポーネントです。JMS API は J2EE 1.3 および J2EE 1.4 プラットフォームに不可欠な部分です。

非同期メッセージではアプリケーションがメッセージ交換によって通信できるため、送信者は受信者と無関係となります。メッセージを送信した送信者は、受信者がそのメッセージを受信または処理するのを待機する必要はありません。ここが同期通信と異なっている点です。同期通信では、別のコンポーネントのメソッドを呼び出しているコンポーネントは、処理が完了し、呼び出し元のコンポーネントにコントロールが戻って来るまで、待機またはブロックする必要があります。

EJB のクライアントアプリケーションの移行

この節では、次の内容について説明します。

JNDI コンテキストにおける EJB の宣言

Sun Java System Application Server 8.2 では、EJB は JNDI サブコンテキスト ejb/ に体系的にマップしています。JNDI 名 Account を EJB に帰属させた場合、Sun Java System Application Server 8.2 によってグローバル JNDI コンテキスト内に、参照 ejb/Account が自動的に作成されます。したがって、この EJB のクライアントは、対応するホームインタフェースを取得するために、ejb/Account をルックアップする必要があります。

ここで、Sun ONE Application Server 6.x に配備されるサーブレットメソッドのコードを調べてみましょう。

ここに提示されるサーブレットでは、ステートフルセッション Bean の BankTeller を呼び出しています。これは JNDI コンテキストのルートにマップされています。ここでコードを検討するメソッドは、EJB のホームインタフェースを取得して、BankTeller オブジェクトをインスタンス化し、このオブジェクトのリモートインタフェースを取得できるようにします。これによって、ユーザーはこのコンポーネントに対するビジネスメソッド呼び出しを作成できるようになります。

/**
   * Look up the BankTellerHome interface using JNDI.
   */
private BankTellerHome lookupBankTellerHome(Context ctx)
      throws NamingException
{
    try
    {
      Object home = (BankTellerHome) ctx.lookup("ejb/BankTeller");
      return (BankTellerHome) PortableRemoteObject.narrow(home, 
	              BankTellerHome.class);
    }
    catch (NamingException ne)
    {
      log("lookupBankTellerHome: unable to lookup BankTellerHome" +
          "with JNDI name ’BankTeller’: " + ne.getMessage() );
      throw ne;
    }
}

このコードでは、ルックアップに対する引数として ejb/BankTeller がすでに使用されているので、Sun Java System Application Server 8.2 に配備するためのコード変更を行う必要はありません。

EJB JNDI 参照の使用方法のまとめ

ここでは、EJB JNDI 参照を使用する場合に注意すべき点をまとめています。注意事項の詳細が、特定のソースアプリケーションサーバーのプラットフォームに固有なものである場合は、その旨を記してあります。

JNDI コンテキスト内の EJB 参照の配置

既存の WebLogic アプリケーション内の JNDI コンテキストのルートに EJB がマップされている場合は、前述の JNDI コンテキスト内の EJB 参照の名前を変更する (これらの参照を JNDI コンテキストルートからサブコンテキスト ejb/ に移動する) 必要があるだけです。

これらの EJB がすでに既存のアプリケーション内の JNDI サブコンテキスト ejb/ にマップ済みの場合は、何も変更する必要はありません。

ただし、Sun Java Studio IDE 内の配備記述子に EJB の JNDI 名を設定している場合は、EJB の JNDI 名にプレフィックス ejb/ を含めないようにすることが重要です。これらの EJB 参照は、Sun Java System Application Server 8.2 によって JNDI ejb/ サブコンテキスト内に自動的に配置されることを忘れないでください。つまり、配備記述子内で EJB に BankTeller という JNDI 名が指定されている場合、Sun Java System Application Server 8.2 によって、この EJB に対する参照は ejb/BankTeller に変換されます。この EJB のクライアントコンポーネントは、ルックアップの実行時に、この JNDI 名を使用する必要があります。

グローバル JNDI コンテキストとローカル JNDI コンテキスト

グローバル JNDI コンテキストを使用した EJB 参照の取得は、Sun Java System Application Server 8.2 に最も有効で適切なアプローチです。しかしながら、J2EE 仕様にできるだけ近い形で、EJB クライアントアプリケーションのローカル JNDI コンテキストを使用して、EJB 参照を取得することをお勧めします。ローカル JNDI コンテキストを使用する場合、まず、クライアント部分の配備記述子内 (Web アプリケーションの場合は web.xml、EJB コンポーネントの場合は ejb-jar.xml) に EJB リソース参照を宣言する必要があります。

CMP エンティティー EJB の移行

ここでは、アプリケーションコンポーネントを EJB 1.1 アーキテクチャーから EJB 2.0 アーキテクチャーに移行する手順について説明します。

CMP 1.1 の Bean を CMP 2.0 に移行するには、まず、特定の Bean が移行可能かどうかを検証する必要があります。この検証作業は次の手順で実行します。

ProcedureBean を移行できるかどうか検証する

  1. ejb-jar.xml ファイルから <cmp-fields> 名に移動し、オプションのタグ <prim-key-field>ejb-jar.xml ファイル内に存在していて、指定された値が設定されているかどうかをチェックします。オプションのタグが存在し、値が設定されていれば、次の手順に進みます。

    ejb-jar.xml 内で <prim-key-class> のフィールド名を検索し、クラス名を取得して、そのクラス内で宣言されている public instance variables を取得します。次に、これらの変数の署名 (名前と大文字/小文字の区別) が上記の <cmp-field> 名に一致するかどうか確認します。見つかった変数を分離します。これらの分離されたフィールドで、大文字で始まっているものがないかどうかチェックします。ある場合は移行できません。

  2. Bean クラスのソースコードを調べて、すべての <cmp-field> 変数の Java の型を取得します。

  3. すべての <cmp-field> 名を小文字に変更し、これらの名前からアクセサを構築します。たとえば、元のフィールド名が Name で、その Java 型が String である場合、アクセス用メソッド署名は次のようになります。

    Public void setName(String name)Public String getName()

  4. これらのアクセス用メソッド署名を、Bean クラス内のメソッド署名と比較します。完全に一致する組み合わせが見つかった場合、移行は実行できません。

  5. カスタム検索メソッドの署名とそれに対応する SQL を取得します。SQL 内に Join、Outer join、または OrderBy が存在するかどうかチェックします。存在する場合は移行できません。EJB QL は Join、Outer join、orOrderBy をサポートしていないからです。

  6. java.util.Enumeration を使用していたすべての CMP 1.1 ファインダが、java.util.Collection を使用する必要があります。これを反映するようにコードを変更してください。CMP2.0 ファインダは java.util.Enumeration を返すことができません。

    実際の移行プロセスの実行方法については、「Bean クラスの移行」を参照してください。

Bean クラスの移行

ここでは、Bean クラスを Sun Java System Application Server 8.2 に移行するために必要な手順を説明します。

ProcedureBean クラスを移行する

  1. Bean クラス宣言の先頭にキーワード abstract を追加します。

    たとえば、Bean クラス宣言が次のような場合

    public class CabinBean implements EntityBean

    次のように変更します。

    abstract public class CabinBean implements EntityBean
  2. アクセサの前に接頭辞としてキーワード abstract を付けます。

  3. 変更後のすべてのアクセサを、Bean クラスのソース (.java) ファイルにクラスレベルで挿入します。

  4. Bean クラスのソースファイル内のすべての cmp フィールドをコメントにします。

  5. protected インスタンス変数の宣言を cmp-field 名から小文字で構築し、これをクラスレベルで挿入します。

  6. すべての ejbCreate() メソッド本体を読み込みます。複数の ejbCreate が存在する場合もあります。

    <cmp-field>=いくつかの値またはローカル変数」というパターンを検索し、「抽象ミュテータメソッド名 (同じ値またはローカル変数)」という表現に置き換えます。

    たとえば、移行前の ejbCreate 本体が次のような場合、

    public MyPK ejbCreate(int id, String name) {
       this.id = 10*id;
       Name = name;   //1
       return null;
    }

    次のように変更します。

    public MyPK ejbCreate(int id, String name) {
       setId(10*id);
       setName(name);   //1
       return null;
    }

    //1 の抽象アクセサのメソッド署名が、EJB 2.0 仕様によって定められた Camel Case 規約に沿っていることに注目してください。また、キーワード「this」が元のソースに存在する場合としない場合がありますが、これは変更後のソースファイルからは削除してください

  7. 手順 5 の ejbPostCreate() メソッドで宣言したすべての protected 変数を初期化します。

    protected 変数の数は ejbCreate() メソッドの数と同じです。この初期化は、初期化コードを次の方法で挿入することで実行されます。

    protected String name;  //from step 5
    protected int id;  //from step 5
    public void ejbPostCreate(int id, String name) {
       name = getName();    /*abstract accessor*/ //inserted in this step
       id  = getId();        /*abstract accessor*/ //inserted in this step
    }
  8. ejbLoad メソッド内部で、protected 変数を Bean のデータベース状態に設定します。

    このためには、次のコード行を挿入します。

    public void ejbLoad() {
       name = getName();    // inserted in this step
       id = getId();        // inserted in this step
       ...                  // existing code
    }
  9. 同じように、データベース状態が更新されるように、ejbStore() 内部の Bean の状態を更新します。

    ただし、ejbCreate() 外部の主キーに対応する setter を更新することはできないので、このメソッド内に setter を含めないでください。次のコード行を挿入します。

    public void ejbStore() {
        setName(name);       //inserted in this step
        setId(id);           //Do not insert this if
                             //it is a part of the
                             //primary key.
        ...                  //already present code
    }
  10. 存在するすべての <cmp-field> 変数名を、手順 5 で宣言した同等の protected 変数名に置き換えます。

    Bean を移行しない場合、少なくとも <cmp-version>1.x</cmp-version> タグを ejb-jar.xml ファイル内の適切な場所に挿入する必要があります。これによって、移行されていない Bean も Sun Java System Application Server 8.2 上で動作し続けます。

ejb-jar.xml の移行

ファイル ejb-jar.xml を Sun Java System Application Server 8.2 に移行するには、次の手順を実行します。

ProcedureEJB 配備記述子を移行する

EJB 配備記述子ファイル ejb-jar.xml を移行するには、ファイルを編集して次の変更を行います。

  1. すべての <cmp-fields> を小文字に変換します。

  2. <reentrant> タグの後に <abstract-schema-name> タグを挿入します。

    < ejb-name> タグ内と同じように、スキーマ名は Bean の名前になり、ias_ という接頭辞が付きます。

  3. 次のタグを <primkey-field> タグの後に挿入します。

    <security-identity>
       <use-caller-identity/>
    </security-identity>
  4. 上記で取得した SQL を使用して、SQL から EJB QL を構築します。

  5. <query> タグと、そこにネストされたすべての子タグを、すべての必要な情報とともに、<security-identity> タグのすぐ後に挿入します。

カスタム検索メソッド

カスタム検索メソッドは、デフォルトの findByPrimaryKey メソッドではなく、findBy メソッドです。このメソッドはエンティティー Bean のホームインタフェース内で定義することができます。EJB 1.1 仕様では、これらの検索メソッドのロジックを定義する標準が規定されていないため、EJB サーバーのベンダーは実装を自由に選択できます。この結果、メソッドの定義に使用される手順は、ベンダーの選択するさまざまな実装によって大きく異なっています。

Sun ONE Application Server 6.x では、標準の SQL を使用して検索ロジックを指定します。

この検索メソッドの定義に関する情報は、エンタープライズ Bean の持続性記述子 (Account-ias-cmp.xml) 内に、次のように格納されています。

<bean-property>
  <property>
    <name>findOrderedAccountsForCustomerSQL</name>
    <type>java.lang.String</type>
    <value>
       SELECT BRANCH_CODE,ACC_NO FROM ACCOUNT where CUST_NO = ?
    </value>
    <delimiter>,</delimiter>
  </property>
</bean-property>
<bean-property>
  <property>
    <name>findOrderedAccountsForCustomerParms</name>
    <type>java.lang.Vector</type>
    <value>CustNo</value>
    <delimiter>,</delimiter>
  </property>
</bean-property>

このように、各 findXXX 検索メソッドには、配備記述子内に対応する 2 つのエントリ (クエリー用の SQL コードと関連するパラメータ) が存在しています。

Sun Java System Application Server 8.2 ではカスタム検索メソッドのロジックも宣言型ですが、これは EJB QL (EJB query language) に基づいています。

EJB-QL 言語はそれ自身に対して使用することはできません。ファイル ejb-jar.xml にある <ejb-ql> タグ内で指定する必要があります。このタグは <query> タグの内部にあります。これは、EJB 内部でクエリー (検索または選択メソッド) を定義するタグです。EJB コンテナでは、各クエリーを検索または選択メソッドの実装に変換することができます。次に <ejb-ql> タグの例を示します。

<ejb-jar>   
  <enterprise-beans>     
    <entity>
      <ejb-name>hotelEJB</ejb-name>       
      ...       
      <abstract-schema-name>TMBankSchemaName</abstract-schema-name>       
      <cmp-field>
      ...       
      <query>         
        <query-method>           
          <method-name>findByCity</method-name> 
            <method-params>             
              <method-param>java.lang.String</method-param> 
            </method-params>         
        </query-method>         
        <ejb-ql>           
          <![CDATA[SELECT OBJECT(t) FROM TMBankSchemaName AS t 
                                         WHERE t.city = ?1]]>
        </ejb-ql>       
      </query>     
    </entity>   
    ...   
  </enterprise-beans> ... 
</ejb-jar>