特別企画

JDBCはもう不要?
パフォーマンス問題を解決したEJBは
実用期を迎える


田澤孝之
日本BEAシステムズ
2002/12/25

EJB1.1の仕様にはシステムのパフォーマンスを低下させる問題点があり、これが「EJBは遅い」「使えない」というイメージを定着させてしまった。EJB2.0ではようやくこの問題が解決され、さらに開発者にメリットをもたらす仕様が追加されている。本稿では、EJB2.0が解決した従来の問題点とEJB2.0のメリットに触れる。また、本稿をお読みいただくにあたって、EJBの基礎知識のない読者は「Webアプリケーションにおける サーバ・サイドJavaの効果的な利用」(前編 EJBの役割とメリット後編 EJBのアーキテクチャ)を事前にお読みいただきたい。(編集局)

EJB利用の現状

 現在サン・マイクロシステムズのサイトでは最新バージョンであるEJB 2.1の仕様がドラフトとして公開されています。EJB 2.1の仕様はJ2EEの1.4に盛り込まれる予定です。詳細はまたの機会にするとして、現時点での最新のバージョンはEJB 2.0、J2EEは1.3です。しかし、2002年12月時点でEJB 2.0を実用レベルで実装しているEJBコンテナはあまりありません。EJB 2.0の仕様は2001年の9月に最終版が出ていることを考えるとEJBコンテナベンダの実装が遅れている事実は否めないでしょう。現在最も利用されているEJBのバージョンはEJB 2.0の1つ前のバージョンであるEJB 1.1です。

 EJBは、すでにJ2EEのサーバサイドコンポーネント、ビジネスロジックを実装するための手段として認知されて、さまざまなシステムで利用されるようになってきました。またコンポーネントの流通という観点からも標準化や共通化といった活動をしている団体がいくつかあります。

 サンの提供する「Java BluePrints」の「Designing Enterprise Applications with the J2EE Platform」の中でも、さらにはStrutsを代表とするWebクライアントのためのMVC(Model-View-Controller)フレームワークでもビジネスロジックであるモデルはEJBで実装するように推奨されています。「なぜEJBなのか」という議論はもう過去のものであるといえるでしょう。

EJB 2.0の変更点

 まず、EJB 1.1の問題点を分析する前に、EJB 2.0で追加、変更された仕様を以下に紹介します。

メッセージ駆動型Bean
 EJBの仕様として3番目となるEJBが定義されました。メッセージ駆動型Bean(MDB:Message Driven Bean)はJMS(Java Message Service)に統合されて、標準のJMSコンシューマとして動作し、J2EE上で非同期処理を実現するためのフレームワークを提供します。ほかのEJBと最も異なる点は、MDBはクライアントに対してのインターフェイスを持ちません。クライアントに対してMDBは隠ぺいされており、関連付けられているJMSのキューもしくはトピックにメッセージを送信するのみです。

コンテナ管理エンティティの改訂
 これまでのエンティティBeanのコンテナ管理の永続化機構(CMP:Container Managed Persistence)を刷新しました。コンテナベンダのための永続化フィールドへの効率的なアクセスを実装するための仕様と、CMPでの関係の仕様の定義です。効率的なアクセスの手法の詳細は後述します。コンテナ管理リレーション(CMR:Container Managed Relation)はこれまでは開発者が独自にオブジェクト間の関係を実装しなければなりませんでしたが、CMRを利用することにより容易に定義し、利用できるようになりました。CMRについても後述します。

ローカルインターフェイスとローカルホームインターフェイス
 分散環境での呼び出しの必要がないローカルクライアントから利用されるEJBで適用します。ローカルインターフェイスとローカルホームインターフェイスを持つEJBに対する呼び出しは、一般的なJavaのメソッドコールと等価となり、下層ではJavaのリモート呼び出しであるRMI(Remote Method Invocation)を利用しません。そのためRMI利用による通信オーバーヘッドがなくなりパフォーマンスが向上します。

EJB-QL
 これまでのコンテナ管理エンティティBeanでのファインダメソッドは、ベンダに依存した方法で検索する条件などをデプロイメント記述子で定義していましたが、標準のクエリ言語EJB-QLが定義されベンダ間での互換性が保たれるようになりました。EJB 2.0のEJB-QLはいくつか問題を持っていますが、それらはEJB 2.1で拡張されることによって解決される予定です。

Selectメソッド
 SelectメソッドはCMPエンティティBeanの内部で、EJB-QLを用いて利用することが可能なメソッドです。デプロイメント記述子の定義はこれまでのファインダメソッドと同等ですが、リモートまたはローカルインターフェイスにSelectメソッドを定義することはできません。

ホームメソッド
 エンティティBeanではこれまでは生成とファインダのメソッドのみEJBホームで公開できましたが、ホームインターフェイスを通じてビジネスロジックも公開できるようになりました。特定のインスタンスに対してではなく、オブジェクト全体に対する操作などを実装することが可能になります。

run-asセキュリティ
 こちらはEJB 1.0であったものがEJB 1.1でなくなり、EJB 2.0で再び復活したものです。実際の実行プリンシパルを、指定したプリンシパルで置き換えることが可能で、「なりすまし」的振る舞いを定義することができます。

CORBAインタオペラビリティ
 異種コンテナ間通信を実現させるために定義されました。RMI over IIOPを利用して通信する場合のために、ネーミングはCosNaming、トランザクションはOTS、セキュリティはCSIv2とシームレスに連携できるようになります。これらは開発者側では意識することはなくコンテナベンダに実装責任があるという仕様です。

 EJB 2.0では新たなEJBであるMDBなども定義されましたが、これらを見てお気付きの方もいらっしゃるでしょう。ほとんどが、EJBコンテナが永続化機構を提供する、コンテナ管理のエンティティBean(CMP)のための仕様の追加や変更になっています。逆をいうと、セッションBeanとBean開発者自身が永続化機構を作りこむ、Bean管理のエンティティBeanに関してはEJB 1.1でほぼ完成形であったことがうかがえるのではないでしょうか。

図1 EJB 2.0の仕様に含まれるEJB
注:EIS(Enterprise Information Sysytem)

 EJBを簡単に復習すると、クライアントとのセッションの間だけ生存する短命なセッションBeanがあります。セッションBeanにはさらに会話状態を保持するものと保持しないものの2種類が存在します。エンティティBeanは長期間存在するオブジェクトを表すEJBです。オブジェクトは永続化されストレージなどに格納されることになります。エンティティBeanにも永続化機構をEJBコンテナが提供するものと開発者が提供するものの2種類が存在します。その中でEJB 2.0で最も手が加えられたのが前者のコンテナ管理永続化のエンティティBean(CMP)です。そして新たにJMSと統合されて非同期処理を実現するメッセージ駆動型Beanが加わりました。

CMP 1.1の問題点とは

 EJB 1.1ではセッションBeanとエンティティBeanの2種類のEJBがありました。EJBはすでにさまざまな方面で利用されていると上述しましたが、厳密にいうと「セッションBeanは利用されているがエンティティBeanの利用は少ない」のが現状なのです。なぜでしょうか? 「EJBは遅い」というコメントを雑誌やWeb上の記事で読まれたことがあると思います。遅いといわれる理由は、エンティティBeanのCMP 1.1の仕様自体が処理速度が出ない仕組みになっている点にありました。

 CMP 1.1の仕様では永続先が定められていません。ほとんどのEJBコンテナがサポートしている永続先はRDBとなっています。永続化オブジェクトをRDBに格納したり取り出したりするということは、EJBコンテナが開発者に代わってSQLを発行して実現することになるのですが、コンテナが通常のJDBCプログラミングと比較してSQLを必要以上にRDBに対して発行してしまいます。これは性能に多大な影響を与えます。上述したCMP 2.0に対する仕様の改訂で、それは浮き彫りになっています。以下実際に実装コードを見ながら説明していきます。

 エンティティBeanのコンテナ管理フィールド(CMPフィールド)の定義を思い出してください。Beanの実装クラスにCMPフィールドをメンバ変数として定義することになっています。そのメンバ変数に変更を加えるためのアクセサメソッドなどを定義し、リモートインターフェイスに公開することになります。まず以下のBeanのソース(抜粋)を見てください。

public class CustomerBean implements javax.ejb.EntityBean {

  // CMP フィールド
  public String customerId;
  public String name;
  public String address;


  // アクセサメソッド
  Public void setCustomerId(String _customerId) {
    customerId = _customerId;
  }
  Public String getCustomerId() {
    return customerId;
  }

  ... ... ...


 さて、このような実装のCMP 1.1ですが、何がいけなかったのでしょうか。それはCMPフィールドをBeanクラスのインスタンス変数として定義しなければならないという仕様でした。ご存じのとおりCMPはコンテナベンダのツールを利用してコンテナクラスを生成します。永続化ストアへのアクセスはそのコンテナクラスが受け持ちます。生成されたコンテナクラスはデータの整合性保持のためにCMPフィールドをしかるべきタイミングで永続化ストアに正しく格納する義務があります。そこまではいいのですが、親クラスで定義されているインスタンス変数は「いつ」「誰が」変更したのかが、コンテナクラスでは判断できません。そのためにメソッドの呼び出し(トランザクションの開始)ごとに、CMPフィールドを永続化ストアから読み出し、呼び出し終了時に書き出すということをしなければならなかったのです。

 つまり、変更の有無を判断できないのでCMPフィールドに変更をしていなくとも、読み出しては書き出すという処理を無条件で実行してしまうのです。これはコンテナベンダには頭の痛い問題でした。もちろんベンダによってはダーティーフラグを用いて書き出しを抑止したり、キャッシュを利用して読み込みを抑止したりというオプションを設けてパフォーマンス対策をしていましたが、根本的な解決にはなっていませんでした。

図2 CMP 1.1でのコンテナクラスの動作

CMP 2.0でどう解決されたか?

 それでは、次にCMP 2.0のソース(抜粋)を見てください。

public abstract class CustomerBean implements javax.ejb.EntityBean {

  // CMP フィールド
  public abstract String getCustomerId();
  public abstract void setCustomerId(String customerId);

  public abstract String getName();
  public abstract void setName(String name);

  public abstract String getAddress();
  public abstract void setAddress(String address);

  ... ... ...

 まずBeanのクラスは抽象(abstract)クラスとして定義をします。そしてこれまでインスタンス変数として定義していたCMPフィールドは抽象アクセサメソッドで定義するように仕様が変更になりました。これでCMP 1.1で持っていた問題がきれいに片付くことになります。コンテナクラスはBeanのクラスを継承して抽象アクセサメソッドの実装をし、実際のCMPフィールドはコンテナクラス内で維持することになります。そのような実装に変わることによってCMPフィールドを「いつ」「誰が」変更したかがはっきりします。「誰」がという点ではコンテナクラス以外では変更をすることができません。要は自分自身です。「いつ」に関してはメソッドが呼ばれたときですがこれも、自身で実装をしているので変更したか、していないかは判断できます。このような仕様の変更により、これまで遅さの原因だったSQLの乱発はCMP 2.0からは抑止されることになりました。

図3 CMP 2.0でのコンテナクラスの動作

EJB 2.0でのメリット

 前述したように、EJB 1.1とEJB 2.0の差分はCMPに対するものがほとんどです。これまで仕様が未完成であったことは否めませんが、仕様の中でも特に注目されているEJBがCMPであるというとらえ方もできるのではないでしょうか。なんといっても永続化プログラミングをしなくて済むのですから、どれだけ開発の効率化が図れるでしょうか。また永続化ストアに依存しない抽象化プログラミングも可能になります。

 では、そのほかのメリットについても解説していきましょう。

CMRの可能性

 コンテナ管理リレーション(CMR)という強力な仕様が追加されました。CMRもそれなりに奥が深いのでここでは簡単に説明しましょう。クラス図を描いていればさまざまな関連が出てくるでしょうし、逆のアプローチでデータスキーマを定義していればテーブル同士の関連が出てくるでしょう。CMRを用いるとそれらをEJBで表現することが容易になります。CMP 2.0はいままでのCMPフィールドに合わせてCMRフィールドが追加されます。CMRフィールドは関連先を表し、1対1、1対nやn対nの関連が表現でき、かつ片方向と双方向の表現ができます。

 「顧客が商品を発注する」という至ってシンプルな例を用いて説明します。顧客と発注の関係は1対nの関係を持ち、双方向の関係を持たせることにします。

図4 顧客と発注のクラス図

 ソース上の定義は簡単です。CMPフィールドと同様にCMRフィールドを定義するのみです。

public abstract class CustomerBean implements javax.ejb.EntityBean {

  // CMP フィールド
  public abstract String getCustomerId ();
  public abstract void setCustomerId (String CustomerId);

  public abstract String getName();
  public abstract void setName(String name);

  public abstract String getAddress();
  public abstract void setAddress(String address);

  // CMR フィールド
  public abstract Set getOrders();
  public abstract void setOrders(Set val);

  ... ... ...

 ここでは発注(orders)というCMRフィールドを定義してあります。顧客(Customer)オブジェクトが複数の発注(Order)オブジェクトとの関連を持っていることを表しています。EJBの定義をするejb-jar.xmlデプロイメント記述子では、<ejb-relation>要素で関係を定義します。以下にejb-jar.xml(抜粋)を示します。

<ejb-jar>

  <ejb-relation>
    <ejb-relation-name>customer-order</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>customer-Has-orders</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <relationship-role-source>
        <ejb-name>customerEJB</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>orders</cmr-field-name>
        <cmr-field-type>java.util.Set</cmr-field-type>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>order-Has-customer</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>
      <relationship-role-source>
          <ejb-name>orderEJB</ejb-name>
      </relationship-role-source>
      <cmr-field>
          <cmr-field-name>customer</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
  </ejb-relation>
</ejb-jar>

 ここでは顧客と発注の関係があり、お互いの方向においてそれぞれ<ejb-relationship-role>要素を定義します。顧客は複数の発注を持つので<cmr-field-name>で定義されるCMRフィールドordersは、<cmr-field-type>でjava.util.Set型と定義されます。逆に発注先の顧客は1つのみとなります。さらに永続化機構の定義としてEJBコンテナに依存したデプロイメント記述子で実際のデータベースのテーブルや外部キーを定義することになります。

 このように、顧客オブジェクトから関連する発注オブジェクトを簡単に定義し、クライアントでは簡単に関連先のオブジェクトを取り出すことが可能になります。ここでは本当に簡単に述べましたが、このCMP 2.0での表現の可能性はEJBをさらなるステージに押し上げることでしょう。

ローカルインターフェイスの適用

 CMRを利用するにはローカルインターフェイスを利用しなければなりません。これは分散環境をまたがった関連の設定ができないことを意味しています。その点を考えるとローカルインターフェイスはCMRを実現するために設定されたものであるというとらえ方もできます。ローカルインターフェイスはこれまで、呼び出しは値呼び出し(Pass by Value)をしなければならなかったのですが、参照呼び出し(Pass by Reference)が可能になります。結果ネットワーク呼び出しコストと、シリアライゼーションコストがなくなります。

図5 ローカルインターフェイスの概念図

 これは最適化の一環でEJB 1.1の段階ですでに実装しているコンテナベンダもありましたが、EJB 2.0からは仕様で明確になったわけです。適用には多少考慮しなければなりませんが、これは簡単な話でリモートクライアントに対してどのような機能を公開するかというだけであり、適切な粒度でEJBをクライアントに公開するきっかけにもなり、性能も向上するので適用できる個所にはどんどん適用するべきでしょう。

まとめ

 ざっとEJB 2.0のエッセンスを述べました。これからEJB 2.0を含むJ2EE 1.3の認定アプリケーションサーバ製品が続々と市場に出てくるでしょう。スペースの関係で細かくは説明できませんでしたが、仕様変更による性能の向上と関連を表現できるデザイン上でのメリットはそれまで遅く使い勝手が悪いといわれたエンティティBeanの「常識」を「非常識」とすることでしょう。

 2003年、J2EEはCMP 2.0がホットになるかもしれません。そして「さらばJDBCプログラミング」となるかもしれません。

[関連記事]
「Webアプリケーションにおける サーバ・サイドJavaの効果的な利用」(Java Solution)
 前編 EJBの役割と基礎
 後編 EJBのアーキテクチャ



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間