[ Topページへ戻る ]
2003.8.19よりアクセス
J2EE 1.4 SDK(Java 2 Platform, Enterprise Edition version 1.4 Software Development Kit)を使ってみます。J2EEは複雑ですが、複雑さに圧倒されていてはマスターできません。弄んでやろうという気構えで、J2EE 1.4 SDKをいじっていきます。
※2005.5現在、Sunの最新Application Serverである2005Q1 UR1に合わせて修正中。
J2EE 1.4 SDKは、Sun Microsystemsが公開しているJ2EEアプリケーション開発用キットです。中核をなすのが、Sun Java System Application Server Platform Editionです。Platform Editionは、開発・運用ともに無償となっています。
J2EE 1.4 SDKの構成は以下のようになっています。
ダウンロードにあたって、これら全てが1つにまとめらたAll-In-One
Bundleと、個別にダウンロードできるSeparate
Bundlesとが選択できるようになっています。
All-In-One Bundleの場合、既にJ2SE 1.4.2以降のJ2SEがインストールされていたとしても、インストールすると専用のディレクトリにJ2SE
1.4.2_07がインストールされます。これを良しとしなければ、Separate
Bundlesで必要なものだけをインストールすればよいでしょう。
Sun MicrosystemsのWebサイトからGet the SDKのリンクをたどってダウンロードします。2005年5月現在は1.4 2005Q1 UR1バージョンです。
既に最新のJ2SE SDKを入れているので、Application Serverのみのパッケージをインストールします。
| 設定項目 | デフォルト | 今回設定例 | 
|---|---|---|
| 管理ユーザー名 | admin | admin | 
| パスワード(8文字以上) | (空白) | ******** | 
| パスワードの確認入力 | (空白) | (空白) | 
| 管理ユーザー名を毎回指定しない | (空白) | チェック | 
| 管理ユーザー名を毎回指定する | (空白) | (空白) | 
| Admin Port | 4848 | 4848 | 
| HTTP Port | 8080 | 8080*1 | 
| HTTPS Port | 8181 | 8181*2 | 
*1) ポート番号8080はWeb系のプログラムで使用する可能性が非常に高い値です。例えば、Tomcat、Oracle、Proxy等。競合の少ない番号を使用したほうがよいでしょう。
*2) 前のバージョンでは、このHTTPSポート番号は1043がデフォルトでした。この番号はWindowsではNetBIOS、HTTPその他の通信でエンドポイントとして割り振られることがあります。マシン起動後しばらくしてからJ2EE Application Serverを起動すると、1043番が使用されており、エラーとなってしまうことがありました。
| 設定項目 | デフォルト | 今回設定例 | 
|---|---|---|
| アプリケーションサーバーを登録 | 有効 | 無効 | 
| 旧バージョンからアップグレード | 無効 | 無効 | 
| ディレクトリの自動配備へのデスクトップショートカットを作成 | 無効 | 無効 | 
| PATHにbinディレクトリを追加 | 有効 | 無効 | 
インストールされるサーバ群は、以下のようなもののようです。
J2EE 1.3.1のRIでは、データベースがCloudscapeでしたが、どうやら1.4ではPointBaseになっているようです。Cloudscapeは、Pure Java ORDBMS(データベース管理システム)です。Cloudscape社がInformix社に買収後発表された製品ですが、現在Informix社はIBMによって買収されています。(実に複雑です)。Pointbaseは、PointBase社が開発したPure Java RDMBS(データベース管理システム)です。
インストール後、スタートメニューのプログラムの中に、J2EE用のメニューが追加されます(インストールしたユーザ固有)。
注)Version 8の画面
ファイル名:sjsas_pe-8_0_0_01-linux.bin
Sun Java System Application Server Platform Edition 8 Update 1のSeparate Bundlesです。
コマンドライン環境からインストールすることができます。
rootでインストールすると、実行時にroot権限が必要になるので、Application Server専用のユーザ・アカウントを設けると良いでしょう。
Supply the admin user's password and override any of the other initial
configuration settings as necessary.
   Admin User [admin] {"<" goes back, "!" exits}:
   Admin User's Password (8 chars minimum):
   Re-enter Password:
   Do you want to store admin user name and password in user preference file
   [yes] {"<" goes back, "!" exits}?
   Admin Port [4848] {"<" goes back, "!" exits}:
   HTTP Port [8080] {"<" goes back, "!" exits}: 8081
   HTTPS Port [1043] {"<" goes back, "!" exits}:
  | 設定項目 | デフォルト | 今回設定例 | 
|---|---|---|
| Admin User Name | admin | admin | 
| Password(min 8 chars) | (空白) | ******** | 
| Store Admin User Name and Password | yes | yes | 
| Admin Port | 4848 | 4848 | 
| HTTP Port | 8080 | 8081 | 
| HTTPS Port | 1043 | 1043 | 
インストールには、GUIを使う方法、コンソール(CUI)を使う方法の2種類あります。
# ./sjsas_pe-8_1_01_2005Q1-solaris-i586-ja-zh-ko-fr-es.bin -console
ディスクスペースの容量を検査中...
Java(TM) 2 Runtime Environment検査中...
Extracting Java(TM) 2 Runtime Environment files...
Extracting installation files...
Java(TM) 2 Runtime Environment を起動しています...
   表示されたソフトウェアライセンス契約のすべての条項を読み、それに
同意しますか  [no] {"<" 戻る, "!" 終了}? yes
Sun Java System Application Server Platform Edition コンポーネントは
次のディレクトリにインストールされます。このディレクトリを「インストー
ルディレクトリ」と呼びます。このディレクトリを使用する場合は Enter キー
を押します。別のディレクトリを使用するには、そのディレクトリの完全パス
を入力してから Enter キーを押します。
   インストールディレクトリ  [/opt/SUNWappserver] {"<" 戻る, "!" 終了}:[Enter]
Sun Java System Application Server は Java 2 SDK を必要とします
   Java 2 SDK 1.4.1 以降へのパスを指定してください。
   1.4.2 以降のバージョンをお勧めします。 [/usr/j2se] {"<" 戻る, "!" 終了} /usr/java
管理ユーザーのパスワードを入力し、必要に応じてその他の初期設定を変更してください。
   管理ユーザー  [admin] {"<" 戻る, "!" 終了}:[Enter]
   管理ユーザーパスワード (8 文字以上): xxxxxxxx
   パスワードの確認入力: xxxxxxxx
   管理ユーザー名とパスワードをユーザー設定ファイルに保存しますか  [yes] 
{"<" 戻る, "!" 終了}? [Enter]
   管理サーバーポート  [4848] {"<" 戻る, "!" 終了}:[Enter]
   HTTP ポート [8080] {"<" 戻る, "!" 終了}:[Enter]
   HTTPS ポート [8181] {"<" 戻る, "!" 終了}:[Enter]
インストールオプションを選択してください。
   アプリケーションサーバーの旧バージョンからアップグレードしますか  [no] {"<" 戻る, "!" 終了}?[Enter]
ディスク容量を確認しています ...
製品 Sun Java System Application Server Platform Edition の次の項目がインストールされます。
製品: Sun Java System Application Server Platform Edition
位置: /opt/SUNWappserver
必要容量: 132.95 MB
-------------------------------------------------------
Application Server
Sun Java System Message Queue 3.6
PointBase Server 4.8
Startup
インストール可能
1. 直ちにインストール
2. やり直す
3. インストールを終了
   どの操作を行いますか  [1] {"<" 戻る, "!" 終了}?[Enter]
インストールしています Sun Java System Application Server Platform Edition
|-1%--------------25%----------------
起動順番は、先にデータベースを立ち上げ、続いてアプリケーションサーバを起動します。
(起動時にデータベースに接続に行くことはなさそうなので、順番は逆でもよさそうです)
RDBMSを実行します。
メニューの[Start PointBase]を実行します。
コマンドラインから、pointbase起動用スクリプトを実行します。
torutk$ $J2EE_HOME/pointbase/tools/serveroption/startserver.sh Server started, listening on port 9092, display level: 0 ... >
J2EEのApplication Server(WebコンテナおよびEJBコンテナ)を実行します。
メニューの[Start Default Server]を実行します。
2行目のLog .... が出てからしばらくたって(数十秒〜数分)、Domain ... 以下の行が表示されます。少々気長に待って下さい。
コマンドラインから、Application Server(Webコンテナ、EJBコンテナ)起動用スクリプトを実行します。
# $J2EE_HOME/bin/asadmin start-domain domain2 Starting Domain domain2, please wait. Log redirected to /opt/SUNWappserver/domains/domain2/logs/server.log. Domain domain2 started. #
domain2としているのは、インストール時に生成されたdomain1ディレクトリがdocrootしかなく不十分であったため、管理コマンドでdomain2を生成して実行しているため。
J2EE 1.4用のBluePrints(サンプルアプリケーション)は、サンプルコードに含まれています。サンプルコードはSDKに含まれていますが、個別にダウンロードもできます。
ZIP形式を解凍すると、samplesというフォルダ以下に展開されます。$J2EE_HOMEの下に展開します。または、別ディレクトリに展開するときは、$J2EE_HOME/samplesの中にあるcommon.propertiesとcommon.xmlをj2eesdk-1_4_01-samples.zipを展開したsamplesディレクトリにコピーします。
| samples
   +--- blueprints
   |        +--- adventure1.0
   |        +--- petstore1.4
   |        +--- smart_ticket1.2
   +--- connectors
   |        +--- inbound
   +--- docs
   +--- ejb
   |        +--- bmp
   |        +--- cmp
   |        +--- mdb
   |        +--- stateful
   |        +--- stateless
   |        +--- subclassing
   |        +--- timersession
   +--- i18n
   |        +--- simple
   +--- jdbc
   |        +--- simple
   |        +--- transactions
   +--- jms
   |        +--- soaptojms
   +--- jsf
   |        +--- carstore
   |        +--- components
   |        +--- guessNumber
   +--- logging
   |        +--- simple
   +--- migration
   |        +--- docs
   +--- pointbase
   |        +--- databases
   +--- quickstart
   +--- rmi-iiop
   |        +--- simple
   +--- webapps
   |        +--- bookstore
   |        +--- caching
   |        +--- security
   |        +--- simple
   +--- webservices
   |        +--- jaxrpc
   |        +--- wsi
   +--- xml
            +--- data
            +--- dom
            +--- sax
            +--- xslt | 
まずPointBaseを起動します。起動手順は前述のとおりです。
続いてJ2EE serverを起動します。起動手順は前述のとおりです。
インストールとデータベースリソース設定のためのコマンド(asant)を実行します。実行にあたって必要な環境設定は以下です。
samples/blueprints/adventure1.0/srcにカレントディレクトリを移動し、以下のコマンドを実行します。途中、J2EE管理者パスワードを聞かれます。以下のコマンドラインの例は、プロンプトが異なる他は、Windows/UNIX共通です。
> asant setup
Build file: build.xml
    :
readpassword:
[sun-appserv-input] Please Enter app-server Admin User Password :
XXXXXXXX
    :
BUILD SUCCESSFUL
Total time: 38 seconds
>
Adventure アプリケーションを構成する各コンポーネントを配備します。
> asant deploy-apps
Build file: build.xml
    :
BUILD SUCCESSFUL
Total time: 2 minutes 36 seconds
>
配備が成功したか検証します。
> asadmin list-components --user admin --password XXXXXXXX OPC <j2ee-application> ConsumerWebsite <j2ee-application> Bank <j2ee-application> ActivitySupplier <j2ee-application> AirlineSupplier <j2ee-application> LodgingSupplier <j2ee-application> Command list-components executed successfully. >
Webブラウザで、URL名/ab/main.screen にアクセスします。
いろいろ旅行を選択して購入してみてください。
adventure1.0/srcの中で
> asant undeploy-apps
Build file: build.xml
    :
BUILD SUCCESSFUL
Total time: 2 minutes 36 seconds
>
または、asadmin list-componentsでアプリケーション名のリストを確認し、asadmin undeploy アプリケーション名 で個別に配備解します。
To Be Continued...
実際にJ2EEアプリケーションを作成してみましょう。あまり単純なサンプルではJ2EEにする理由もないので、多少はJ2EEの意義が見えるような、それでいて全体像の把握が易しい題材を考えてみます。
多少データベースを使う必然性があって、かつシンプルな題材として、書籍管理システムを作ってみます。といっても単に書籍目録ではアプリケーションにならないので、ここでは「Java読書会」における課題図書を扱うアプリケーションを作成します。
とりあえず要求になりそうなことを羅列します。この中からどれかをピックアップしてサンプルを作成します。
まず、2番目の要求を実現するステートレスセッションBeanを作成してみます。
ここではエディタ+素のJDKで基礎的な作成方法を会得します。基礎が分かれば、あとは便利な開発環境を好みでチョイスして使えばよいのです。基礎を知っていれば、チョイスすることが可能になります。
| 環境変数 | 設定例 | 備考 | 
|---|---|---|
| J2EE_HOME | C:\java\j2ee1.4 | |
| PATH | %J2EE_HOME%\bin | 既存のPATHに追加 | 
| CLASSPATH | %J2EE_HOME%\lib\j2ee.jar | 既存のCLASSPATHに追加 | 
J2EEアプリケーションでは、EJBが中核になります。もちろんEJBなしにWeb層に自前でビジネスロジック層を付け加えて作成することもありますが、「専用クライアント」や「C++アプリケーション」からもアクセスするにはEJBが便利です。
EJBコンポーネントの開発手順は以下のようになります。
J2EEではBeanプロバイダとかアセンブラとかデプロイヤといった役割が定義されていますが、ソフトウェア開発の実体には合っていないため分かりにくい定義となっています。
おおまかに分類すると、1.〜4.は開発するEJBコンポーネントの外部仕様(インタフェース)を決める設計作業、5.および6.はEJBコンポーネントの実装作業、7.は製造作業、8.はインストール作業にあたります。
*1) 手で記述する方法は書式を理解して厳密に書かねばならず、エラーを除くのが大変なので、今回はデプロイツールで生成します
EJBには命名規約があります。(J2EE 1.4 Tutorial<2004.3.22版>の表23-2参照)
| 項目 | 規約 | 例 | 
|---|---|---|
| Enterprise Bean名 | <基幹名>EJB | AccountEJB | 
| EJB JAR表示名 | <基幹名>JAR | AccountJAR | 
| Beanクラス | <基幹名>Bean | AccountBean | 
| ホームインタフェース | <基幹名>Home | AccountHome | 
| リモートインタフェース | <基幹名> | Account | 
| ローカルホームインタフェース | <基幹名>LocalHome | AccountLocalHome | 
| ローカルインタフェース | <基幹名>Local | AccountLocal | 
| Abstract schema | <基幹名> | Account | 
EJB2.0からはローカルインタフェースが追加されたので、従来のようにEJBのコールにはオーバーヘッドがかかる、といった問題が同一JavaVM上のクライアントからの呼び出しにおいては回避できます。(でも同一JavaVM上にあるクライアントって何だろう?)
クライアントからはこのインタフェースが利用できるサービスの定義となります。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Assignment extends EJBObject {
    /**
     * 現在の課題図書の題名を取得するメソッド。
     */
    String getCurrentTitle() throws RemoteException;
    
}
 | 
同じJavaVM上に限ってこのインタフェースを利用できます。リモートインタフェースよりもマーシャリング処理、ネットワーク通信が無い分高速に実行されます。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBLocalObject;
public interface AssignmentLocal extends EJBLocalObject {
    /**
     * 現在の課題図書の題名を取得するメソッド。
     */
    String getCurrentTitle();
  
} | 
いわゆるファクトリ役となるインタフェースです。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBHome;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
public interface AssignmentHome extends EJBHome {
    Assignment create() throws RemoteException, CreateException;
} | 
同一JavaVM上から利用される際のファクトリ役となるインタフェースです。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBLocalHome;
import javax.ejb.CreateException;
public interface AssignmentLocalHome extends EJBLocalHome {
    Assignment create() throws CreateException;
} | 
ビジネスロジックを実装するクラスとなります。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.SessionBean;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionContext;
public class AssignmentBean implements SessionBean {
    public AssignmentBean() {
    }
    public void ejbCreate() {
    }
    public void ejbRemove() {
    }
    public void ejbActivate() {
    }
    public void ejbPassivate() {
    }
    public void setSessionContext(SessionContext aContext) {
        context = aContext;
    }
    public String getCurrentTitle() {
        return "EJBデザインパターン";
    }
    private SessionContext context;
} | 
エディタで作成したかったのですが、記述がエラーとなるので、アプリケーションサーバ付属のツールで作成するのがよいでしょう。ツールを使った手順は後述します。
| <?xml version="1.0" encoding="Windows31J"> <!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> <session> <ejb-name>Assignment</ejb-name> <home>jp.gr.java_conf.torutk.ejb.javareading.AssignmentHome</home> <remote>jp.gr.java_conf.torutk.ejb.javareading.Assignment</remote> <local-home>jp.gr.java_conf.torutk.ejb.javareading.AssignmentLocalHome</local-home> <local>jp.gr.java_conf.torutk.ejb.javareading.AssignmentLocal</local> <ejb-class>jp.gr.java_conf.torutk.ejb.javareading.AssignmentBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar> | 
SunのJ2EE SDK 1.4のデプロイツールで生成された配備記述子の例です。J2EE 1.3では、DTDベースのスキーマでしたが、J2EE 1.4ではXML Schemaベースのスキーマに変更になっているようです。以下の記事が参考になるでしょう。
| <?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
 http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
  <display-name xml:lang="ja">assignmentJAR</display-name>
  <enterprise-beans>
    <session>
      <ejb-name>AssignmentEJB</ejb-name>
      <home>jp.gr.java_conf.torutk.ejb.javareading.AssignmentHome</home>
      <remote>jp.gr.java_conf.torutk.ejb.javareading.Assignment</remote>
      <local-home>jp.gr.java_conf.torutk.ejb.javareading.AssignmentLocalHome</local-home>
      <local>jp.gr.java_conf.torutk.ejb.javareading.AssignmentLocal</local>
      <ejb-class>jp.gr.java_conf.torutk.ejb.javareading.AssignmentBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Bean</transaction-type>
      <security-identity>
        <use-caller-identity/>
      </security-identity>
    </session>
  </enterprise-beans>
</ejb-jar> | 
おまけに、J2EE SDK 1.4のデプロイツールで生成されるベンダ固有配備記述子の例です。
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Sun ONE Application Server 8.0 EJB 2.1//EN"
 "http://www.sun.com/software/sunone/appserver/dtds/sun-ejb-jar_2_1-0.dtd">
<sun-ejb-jar>
  <enterprise-beans>
    <name>assignmentJAR</name>
    <unique-id>1536171059</unique-id>
    <ejb>
      <ejb-name>AssignmentEJB</ejb-name>
      <jndi-name>AssignmentEJB</jndi-name>
    </ejb>
  </enterprise-beans>
</sun-ejb-jar>
 | 
まず、実装したJavaのソースコードをコンパイルします。
C:\ejbdev> javac -d dist src\jp\gr\java_conf\torutk\ejb\javareading\*.java C:\ejbdev>
EJBコンポーネントの配備用アーカイブを作成します。
次に、配備ツール(deploytool)をコマンドラインから起動します。Windows環境ならスタートメニューからも実行できます(Deploytool)。
C:\ejbdev> deploytool C:\ejbdev>
すると、ウィンドウが開きます。
メニュー[File]→[New]→[Enterprise Bean...]を選択します。
すると、EJB JAR作成ウィザードが起動します。
Java Locationの意味は、
ここでは、1.の新規EJB用JARを単独で生成していきます。
JAR Namingでは、新規に生成するJARファイルのパスを指定します。[Browse...]ボタンを押すとファイル選択ダイアログが出るので作成する場所とJARファイル名を指定します。なお、EJBの命名規約では、EJB用JARファイルの名前にはJARと付けることになっています。
assignmentJAR
[Edit...]ボタンを押し、EJB用JARに追加するクラス群をディレクトリ込みで指定します。ここでは、コンパイルした結果のjpディレクトリを指定しました。
[Next>]ボタンを押します。
Enterprise Bean Classのコンボボックスをクリックすると、先ほど追加したクラスの中からBeanクラスがリストされます。今回はBeanクラスは1つだけ(AssignmentBean)なので、これを選択します。この選択時にEnterprise Bean NameとEnterprise Bean Typeは自動的に補完されたので、残りのLocal InterfacesとRemote Interfacesのコンボボックスをクリックしてクラスを指定します。これらも先ほど追加したクラスの中から選択可能な候補がリストされますが、今回は1つずつしかないのでそれを選択していきます。選択後、[Next>]ボタンを押します。
次の画面(図は省略)では、WebサービスとしてBeanを扱うかどうか聞かれます。今回はWebサービスを扱わないのでNoを選択して[Next>]ボタンを押し、最後の画面で[Finish]ボタンを押します。
ウィザードが終了し、設定した情報がdevloytoolの表示に反映されています。
まず、ターゲットマシン上でアプリケーションサーバを起動します。Windows環境ならスタートメニューからも実行できますが(Start Default Domain)、ここではコマンドから実行してみます。
C:\ejbdev> asadmin start-domain domain1 Starting Domain domain1, please wait. Log redirected to D:\java\j2ee1.4\domains\domain1\logs\server.log. Domain domain1 started. C:\ejbdev>
続いて、生成したJARファイルの配備を実施します。assignmentJARを選択した状態で、メニュー[Tools]→[Deploy...]を選択します。接続先サーバは同一マシンで開発している場合はlocalhost:4848で、User Name:とPassword:はJ2EEインストール時に設定したものを入力します。
ここで、後ほどクライアントプログラムを作成するのに必要なクラスを含むJARファイルを生成するために、"Return Client Jar"欄にチェックを付けておきます(下図の赤い丸で囲まれた個所)。なお、ここで忘れてしまっても後で生成できます。
[OK]ボタンを押すと、配備作業が開始されます。
配備が完了すると、deploytool上で配備先サーバ(例:localhost:4848)を選択して右側に表示されるオブジェクトに追加されていることが分かります。一度配備すると、アプリケーションサーバを終了して再起動しても、配備された状態になっています。
"Return Client Jar"欄にチェックを付けていると、指定したディレクトリにクライアント・プログラムが必要とするクラスが含まれたJARファイルが生成されます。忘れてしまったなら、上記画面の[Client Jar...]ボタンを押してJARファイル出力先ディレクトリを指定すると、クライアント用JARファイルが生成されます。
E:\work> jar tf assignmentJARClient.jar jp/gr/java_conf/torutk/ejb/javareading/_AssignmentHome_Stub.class jp/gr/java_conf/torutk/ejb/javareading/_Assignment_Stub.class jp/gr/java_conf/torutk/ejb/javareading/AssignmentHome.class jp/gr/java_conf/torutk/ejb/javareading/Assignment.class jp/gr/java_conf/torutk/ejb/javareading/AssignmentLocalHome.class META-INF/sun-j2ee-ri.project jp/gr/java_conf/torutk/ejb/javareading/AssignmentBean.class jp/gr/java_conf/torutk/ejb/javareading/AssignmentLocal.class META-INF/ejb-jar.xml META-INF/sun-ejb-jar.xml E:\work>
配備された状態を探ってみましょう。$J2EE_HOME/domainsディレクトリの中には、domain1や管理者が作成したドメインディレクトリがあります。J2EEサーバをdomain1で起動した場合、配備したコンポーネントはこのdomain1の中にある形式で置かれます。
| $J2EE_HOME
   +-- domains
         +-- domain1
               +-- applications
                     +-- j2ee-apps
                     +-- j2ee-modules
                           +-- assignmentJAR
                                 +-- META-INF
                                 +-- assignmentJARClient.jar
                                 +-- jp
                                     +- gr
                                        +- java_conf
                                           +- torutk
                                              +- ejb
                                                 +- javareading
                                                    +- Assignment.class
                                                    +- AssignmentBean.class
                                                    +- AssignmentHome.class
                                                    +- AssignmentLocal.class
                                                    +- AssignmentLocalHome.class | 
EJBコンポーネントを使用するクライアントプログラムを作成します。ここでは、コンソールプログラムを作成します。
| package client;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import jp.gr.java_conf.torutk.ejb.javareading.Assignment;
import jp.gr.java_conf.torutk.ejb.javareading.AssignmentHome;
public class SimpleClient {
    public static void main(String[] args) {
        try {
            Context initialContext = new InitialContext();
            Object homeRef =
                initialContext.lookup("AssignmentEJB");
            AssignmentHome home = (AssignmentHome)PortableRemoteObject.narrow(
                homeRef, AssignmentHome.class
            );
            Assignment ejb = home.create();
            String title = ejb.getCurrentTitle();
            System.out.println("現在の課題図書は、" + title + " です。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
} // SimpleClient | 
ものの本では、EJBコンポーネントをlookupするときに指定する名前は、
java:comp/env/ejb/AssignmentEJB
のように、"java:comp/env/ejb"を付与しています。しかし、J2EE 1.4 SDKではこれをつけた名前でlookupしても何故かNameNotFoundとなってしまいます。
java:comp/envはどうやらコンテナ上で動作するプログラムについてのみ使用可能な接頭辞のようです。独立したクライアントプログラム上で指定しても意図とは違う働きになってしまう模様。
コンパイルにあたっては、J2EE APIを含むj2ee.jarと、EJBコンポーネント配備時に生成したクライアント用JARファイルが必要となります。
E:\work>javac -d classes -classpath %J2EE_HOME%\lib\j2ee.jar;lib\assignmentJARClient.jar src\client\SimpleClient.java E:\work>
クライアントを実行するには、JNDIの設定が必要となります。JNDIの設定には、システムプロパティとしてコマンドラインで指定する方法と、jndi.propertiesファイルをCLASSPATHで指定したディレクトリに置く方法があります。
| JNDIプロパティのキー | 設定値 | 
|---|---|
| java.naming.factory.initial | com.sun.jndi.cosnaming.CNCtxFactory | 
| java.naming.provider.url | iiop://localhost:3700 | 
E:\work>java -cp classes;lib\assignmentJARClient.jar;%J2EE_HOME%\lib\j2ee.jar -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory -Djava.naming.provider.url=iiop://localhost:3700 client.SimpleClient 現在の課題図書は、EJBデザインパターン です。 E:\work>
| pjava.naming.factory.initial = com.sun.jndi.cosnaming.CNCtxFactory java.naming.provider.url = iiop://localhost:3700 | 
上記のファイルを、classesの下に置いておくと、内容がJavaVM起動時にシステムプロパティに追加されます。
E:\work>java -cp classes;lib\assignmentJARClient.jar;%J2EE_HOME%\lib\j2ee.jar client.SimpleClient 現在の課題図書は、EJBデザインパターン です。 E:\work>
続いて、書籍情報を表すエンティティBeanを作成してみます。
書籍エンティティBeanは、リモートから直接使用せず、セッションBeanを介して使用する設計手法(セッション・ファサード・パターン)を採用します。したがって、ローカルインタフェースのみ作成します。
エンティティBeanには、永続化処理を開発者自身が記述しなくてはならないBMPと永続化処理をコンテナに自動生成させるCMPとがあります。BMPの場合はBeanのコードがとても冗長になってしまうので、CMPを使用します。
設計要素として書籍情報には以下の属性を持つことにします。
| 属性名(日本語) | 属性名 | 型 | 
|---|---|---|
| 書籍名 | title | String | 
| 著者名 | author | String | 
| ISBNコード | isbn | String | 
| 出版社名 | publisher | String | 
| 価格 | price | Integer | 
書籍情報としては、かなり簡略化したものです。実際には、複数著者対応、翻訳本のときの原題、原著者、原出版社、消費税、出版年月日、頁数、大きさなどが必要になると思います。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBLocalObject;
public interface BookLocal extends EJBLocalObject {
    String getTitle();
    String getAuthor();
    String getIsbn();
    String getPublisher();
    Integer getPrice();
}// BookLocal | 
この例では、setメソッドは宣言していません。setメソッドは、エンティティを変更する必要があれば宣言すればよいでしょう。ただし、主キー(Primary Key)に指定する属性のsetメソッドは宣言することができません。
createメソッドには、書籍情報の属性を全て指定することにします。
検索メソッドとしては、必須のfindByPrimaryKeyメソッド以外に、出版社毎の書籍リストを得るfindByPublisherメソッド、全ての書籍リストを得るfindAllBooksメソッドを定義します。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EJBLocalHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.util.Collection;
public interface BookLocalHome extends EJBLocalHome {
    BookLocal create(String isbn, String title, String author,
                     String publisher, int price) throws CreateException;
    BookLocal findByPrimaryKey(BookPK key) throws FinderException;
    Collection findByPublisher(String publisher) throws FinderException;
    Collection findAllBooks() throws FinderException;
}// BookLocalHome
 | 
エンティティBeanの属性の1つを主キーとする場合、findByPrimaryKeyメソッドの引数は主キーとした属性の型となります。ただし、複合キーのように複数の属性から主キーを構成する場合は、主キークラスを定義してそれを使用します。
属性の1つを主キーにする場合も、主キークラスを定義して使用することで、後日主キーを変更したり複合キーにする場合にも最小限の変更で済みます。主キークラスを定義しておいた方が実践的であるように思います。
ISBNコードを主キーとします。
| package jp.gr.java_conf.torutk.ejb.javareading;
import java.io.Serializable;
public class BookPK implements Serializable {
    public BookPK() {
    } // BookPK constructor
    public BookPK(String anIsbn) {
        isbn = anIsbn;
    }
    public String toString() {
        return isbn.toString();
    }
    public int hashCode() {
        return isbn.hashCode();
    }
    public boolean equals(Object book) {
        return ((BookPK)book).isbn.equals(isbn);
    }
    String isbn;
    
} // BookPK | 
ローカルインタフェース設計時に定義した属性に対するget/setメソッドをabstractメソッドとして定義します。ejb必須メソッドの空実装を定義します。createメソッドに対応するejbCreateメソッドではcreate時に渡される引数をsetメソッドを利用して設定する実装を記述します。デザインパターンで云うところのテンプレート・メソッド・パターンですね。
| package jp.gr.java_conf.torutk.ejb.javareading;
import javax.ejb.EntityContext;
import javax.ejb.EntityBean;
import java.rmi.RemoteException;
import javax.ejb.RemoveException;
import javax.ejb.EJBException;
import javax.ejb.CreateException;
public abstract class BookBean implements EntityBean {
    public BookBean() {
    } // BookBean constructor
    public abstract String getTitle();
    public abstract void setTitle(String title);
    public abstract String getAuthor();
    public abstract void setAuthor(String author);
    public abstract String getIsbn();
    public abstract void setIsbn(String isbn);
    public abstract String getPublisher();
    public abstract void setPublisher(String publisher);
    public abstract Integer getPrice();
    public abstract void setPrice(Integer price);
    
    // Implementation of javax.ejb.EntityBean
    public void ejbActivate() throws EJBException, RemoteException {        
    }
    public void ejbLoad() throws EJBException, RemoteException {
    }
    public void ejbPassivate() throws EJBException, RemoteException {
    }
    public void ejbRemove() throws RemoveException, EJBException, RemoteException {
    }
    public void ejbStore() throws EJBException, RemoteException {
    }
    public void setEntityContext(EntityContext entityContext) throws EJBException {
        context = entityContext;
    }
    public void unsetEntityContext() throws EJBException {
        context = null;
    }
    public void ejbPostCreate(String isbn, String title, String author,
                              String publisher, int price) {
    }
    public BookPK ejbCreate(String isbn, String title, String author,
                          String publisher, int price) throws CreateException {
        setIsbn(isbn);
        setTitle(title);
        setAuthor(author);
        setPublisher(publisher);
        setPrice(new Integer(price));
        return null;
    }
    protected EntityContext context;    
} // BookBean | 
Exception in thread "main" java.rmi.ServerException: RemoteException occurred in
 server thread; nested exception is:
        java.rmi.RemoteException: Bean class for ejb [PhraseBean] does not defin
e a method corresponding to [Home] interface method [public abstract helloagain.
dimbula.phrase.Phrase helloagain.dimbula.phrase.PhraseHome.create(java.lang.Stri
ng) throws javax.ejb.CreateException,java.rmi.RemoteException]
まず、実装したJavaのソースコードをコンパイルします。
C:\ejbdev> javac -d dist src\jp\gr\java_conf\torutk\ejb\javareading\*.java C:\ejbdev>
J2EE SDKでは、Deployツールを使用して配備記述子を作成します。
Deployツールを起動して、メニュー[File]→[New]→[Enterprise
Bean...]を選択します。New Enterprise Bean Wizardが立ち上がります。
を選択し、JAR NameにbookJARを指定、上記で作成した4つのクラス(BookBean.class/BookLocal.class/BookLocalHome.class/BookPK.class)をContentsに加えます。
これで、最低限の設定がなされたBookJARが生成されます。ただし、永続化関連の設定が不足しているので、続いてDeploytool上で設定を続けます。
Deploytoolの左側ツリービューから、上述で生成したBookBeanを選択します。右側でEntityタブを選択すると、永続化関連の設定パネルが表示されます。
[Find/Select Queries...]ボタンを選択し、ホームインタフェースで宣言した検索メソッド findByPublisherとfindAllBooksの検索ロジックをEJB QLで定義します。
| 検索メソッド | EJB QL | 
|---|---|
| findAllBooks | SELECT OBJECT(b) FROM BookBean b | 
| findByPublisher | SELECT OBJECT(b) FROM BookBean b WHERE b.publisher = ?1 | 
EJB QLにおいて、FROM句で指定する名前は、前の設定画面でAbstract Schema Name欄に記載されている名前です。
EJB QLでは、Abstract Schema Nameに対してロジックを定義しました。EJBコンテナは、このAbstract Schemaの仮想的なテーブルから実際のデータベースへの接続への対応付けを知っている必要があります。そこで、まずJDBC接続プールおよびJDBCリソースの設定をApplication Serverに指定しておきます。この両者の設定は、DeploytoolではなくApplication Serverの管理コンソールを使用して実施します。
| 設定項目 | 設定内容 | |
|---|---|---|
| General Settings | ||
| Name | PointBasePool | |
| Data Source Class Name | com.pointbase.xa.xaDataSource | |
| Resource Type | javax.sql.XADataSource | |
| Pool Settings | ||
| Initial and Minumum Pool Size | 8 | |
| Maximum Pool Size | 32 | |
| Pool Resize Quantity | 2 | |
| Idle Timeout | 300 Seconds | |
| Max Wait Time | 60000 Milliseconds | |
| Connection Validation | ||
| Connection Validation | Not Required | |
| Validation Method | auto-commit | |
| Table Name | - | |
| On Any Failure | Not Close All Connections | |
| Transaction Isolation | ||
| Transaction Isolation | - | |
| Isolation Level | Not Guaranteed | |
| Properties | ||
| DatabaseName | jdbc:pointbase:server://localhost:9092/sun-appserv-samples | |
| Password | pbPublic | |
| User | pbPublic | |
永続化方法を指定します。Entityタブで[CMP Database(Sun-specific)...]ボタンを選択すると、CMPデータベース設定画面が表示されます。CMP DatabaseのCMP ResourceにあるJNDI Nameフィールドに、このCMPエンティティBeanを永続化する際に使用するリソースのJNDI名を記述します。
JDBCリソースにあらかじめ設定されている"jdbc/PointBase"を使用します。独自のJDBCリソースを使用する場合は、前述のJDBCリソース/JDBC接続プールの設定を行っておきます。
CMPエンティティBeanの本題ともいえる、永続化フィールドとデータベースとのマッピングを指定します。エンティティBeanを先に設計しており、それに合わせてデータベースのテーブルを作成することが出来る場合は、テーブルをエンティティBeanに合わせて自動生成させる方法が簡単です。既にテーブルが存在する場合は、手でマッピング情報を記述したファイルを作成し、それをDeploytoolで読み込ませます。
今回は、設計したBeanに合わせてデータベーステーブルを作成すればよいので、[Create Database Mappings...]をクリックし、"Automatically Generate Necessary Tables"にチェックを付けて[OK]をクリック。すると、RDBMSのテーブルとCMPエンティティBeanの永続化対象フィールドのマッピングが生成されます。
次に、[Table Generation Settings]ボタンをクリックし、デプロイ・アンデプロイ時にデータベースのテーブルを生成・削除するかしないかを選択します。
この設定の結果、以下のようなJARファイル構成となります。
| <?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" version="2.1"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
 <display-name xml:lang="ja">bookJAR</display-name>
 <enterprise-beans>
  <entity>
   <ejb-name>BookBean</ejb-name>
   <local-home>jp.gr.java_conf.torutk.ejb.javareading.BookLocalHome</local-home>
   <local>jp.gr.java_conf.torutk.ejb.javareading.BookLocal</local>
   <ejb-class>jp.gr.java_conf.torutk.ejb.javareading.BookBean</ejb-class>
   <persistence-type>Container</persistence-type>
   <prim-key-class>jp.gr.java_conf.torutk.ejb.javareading.BookPK</prim-key-class>
   <reentrant>false</reentrant>
   <cmp-version>2.x</cmp-version>
   <abstract-schema-name>BookBean</abstract-schema-name>
   <cmp-field>
    <description xml:lang="ja">no description</description>
    <field-name>title</field-name>
   </cmp-field>
   <cmp-field>
    <description xml:lang="ja">no description</description>
    <field-name>price</field-name>
   </cmp-field>
   <cmp-field>
    <description xml:lang="ja">no description</description>
    <field-name>isbn</field-name>
   </cmp-field>
   <cmp-field>
    <description xml:lang="ja">no description</description>
    <field-name>publisher</field-name>
   </cmp-field>
   <cmp-field>
    <description xml:lang="ja">no description</description>
    <field-name>author</field-name>
   </cmp-field>
   <security-identity>
    <use-caller-identity/>
   </security-identity>
   <query>
    <query-method>
     <method-name>findAllBooks</method-name>
     <method-params/>
    </query-method>
    <ejb-ql>SELECT OBJECT(b) FROM BookBean b</ejb-ql>
   </query>
   <query>
    <query-method>
     <method-name>findByPublisher</method-name>
     <method-params>
      <method-param>java.lang.String</method-param>
     </method-params>
    </query-method>
    <ejb-ql>SELECT OBJECT(b) FROM BookBean b WHERE b.publisher = ?1</ejb-ql>
   </query>
  </entity>
 </enterprise-beans>
</ejb-jar> | 
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.0 EJB 2.1//EN"
 "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_2_1-0.dtd">
<sun-ejb-jar>
  <enterprise-beans>
    <name>bookJAR</name>
    <ejb>
      <ejb-name>BookBean</ejb-name>
    </ejb>
    <cmp-resource>
      <jndi-name>jdbc/PointBase</jndi-name>
      <create-tables-at-deploy>true</create-tables-at-deploy>
      <drop-tables-at-undeploy>true</drop-tables-at-undeploy>
      <database-vendor-name>POINTBASE</database-vendor-name>
      <schema-generator-properties>
        <property>
          <name>use-unique-table-names</name>
          <value>false</value>
        </property>
        <property>
          <name>java-to-database</name>
          <value>true</value>
        </property>
      </schema-generator-properties>
    </cmp-resource>
  </enterprise-beans>
</sun-ejb-jar> | 
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-cmp-mappings PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.0 OR Mapping//EN"
 "http://www.sun.com/software/appserver/dtds/sun-cmp-mapping_1_1.dtd">
<sun-cmp-mappings>
  <sun-cmp-mapping>
    <schema>bookJAR_jar</schema>
    <entity-mapping>
      <ejb-name>BookBean</ejb-name>
      <table-name>BOOKBEAN</table-name>
      <cmp-field-mapping>
        <field-name>author</field-name>
        <column-name>BOOKBEAN.AUTHOR</column-name>
      </cmp-field-mapping>
      <cmp-field-mapping>
        <field-name>isbn</field-name>
        <column-name>BOOKBEAN.ISBN</column-name>
      </cmp-field-mapping>
      <cmp-field-mapping>
        <field-name>price</field-name>
        <column-name>BOOKBEAN.PRICE</column-name>
      </cmp-field-mapping>
      <cmp-field-mapping>
        <field-name>publisher</field-name>
        <column-name>BOOKBEAN.PUBLISHER</column-name>
      </cmp-field-mapping>
      <cmp-field-mapping>
        <field-name>title</field-name>
        <column-name>BOOKBEAN.TITLE</column-name>
      </cmp-field-mapping>
    </entity-mapping>
  </sun-cmp-mapping>
</sun-cmp-mappings>
 | 
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Sun ONE Application Server 8.0 EJB 2.1//EN"
 "http://www.sun.com/software/sunone/appserver/dtds/sun-ejb-jar_2_1-0.dtd">
<sun-ejb-jar>
  <enterprise-beans>
    <name>assignmentJAR</name>
    <unique-id>1536171059</unique-id>
    <ejb>
      <ejb-name>AssignmentEJB</ejb-name>
      <jndi-name>AssignmentEJB</jndi-name>
    </ejb>
  </enterprise-beans>
</sun-ejb-jar> | 
これで、配備可能なJARファイルが作成できました。ただし、ローカルインタフェースのみ作成したエンティティBeanなので、実際に利用するにはセッションファサードとして機能するセッションBeanを用意して、エンティティBeanとセッションBeanのそれぞれのJARから一つのアプリケーション・アーカイブ(EAR)を作成し、これを配備します。
先ほど作成したBookBeanを利用するセッションBeanを作成します。最初に開発したAssignmentBeanを修正して使用することにします。セッションファサードは、リモートから使用することを前提にしているので、今回はローカルインタフェースは作成しません。
エンティティBean"Book"はローカルインタフェースのみ扱えるので、セッションファサード・パターンを適用します。この場合、セッションBeanがBookを扱うビジネス・メソッドをクライアントに提供することになります。ここでは、図書の生成、図書名一覧、現在課題図書名取得の3つをビジネス・メソッドとします。
createBookメソッド、getAllTitlesメソッドを追加します。Bookを生成するのに必要な情報を引数にして呼び出します。getAllTitlesのように複数の結果を返すメソッドでは、例のようにCollectionを使用するほか、独自のデータクラスを作成する方法(Data Transfer Objectパターン)もあります。
| public interface Assignment extends javax.ejb.EJBObject {
    void createBook(String isbn, String title, String author,
                    String publisher, int price) throws RemoteException;
    Collection getAllTitles() throws RemoteException;
    String getCurrentTitle() throws RemoteException;
} // Assignment | 
セッションファサードは、クライアントからの要求を実際に処理を受け持つEnterprise Beanに委譲します。今回の例では、"Book"エンティティBeanに委譲します。セッションファサードが公開する3つのメソッドcreateBook、getAllTitles、およびgetCurrentTitleの実装に委譲コードを記述します。
createBookメソッドでは、委譲先の"Book"エンティティBeanを、EJBの作法にのっとってJNDIで検索してホームインタフェースを取得し、続いてホームインタフェースのcreateメソッドを呼ぶことによって新しい"Book"エンティティを作成します。"Book"エンティティBeanはローカルインタフェースしか公開していませんので、JNDIからルックアップしてきたインスタンスを直接キャストして使用しています。
JNDIルックアップ時に指定する"Book"エンティティBeanのJNDI名は、"java:comp/env/ejb/Book"としています。これは、"Book"エンティティBean側で指定している名前とは一致していませんが、プログラムコード中で使用するJNDI名は配備記述子で本来の名前とマッピングするため問題ありません。逆に、このような仕組みにしておかないと、JNDI名変更のたびにソースコードに手を入れなくてはなりません。
createBookメソッドが、内部で例外が生じたときに何をすべきかについては今後の課題です。CreateException
をスローするべきか、単にログ等に記録するに留めるか、それとも戻り値でエラー有無を通知するべきか、といったところが検討すべき点です。
getAllTitlesメソッドでも、まずはEJBの作法にのっとってJNDIで検索してホーム・インスタンスを取得します。ホーム・インタフェースに宣言されている検索メソッド(findAllBooks)を利用して書籍一覧を獲得し、その中から題名を新たなCollectionに追加して題名の一覧を返却します。
題名とISBNコードをセットで返却したい場合は、HashMapを使用してもよいでしょう。
getCurrentTitleでも、まずはEJBの作法にのっとってJNDIで検索してホーム・インスタンスを取得します。ホーム・インタフェースに宣言されている検索メソッド(findByPrimaryKey)を利用するためにISBNコードから主キーインスタンスを生成し、"Book"エンティティBeanを取得して、その題名をリターンします。
エラー処理はいまいちですが・・・。
| public class AssignmentBean implements SessionBean {
    // ... 省略
    public void createBook(String isbn, String title, String author,
                           String publisher, int price)
    {
        try {
            Context context = new InitialContext();
            BookLocalHome home = (BookLocalHome)context.lookup(BOOK_JNDI_NAME);
            BookLocal book = create(isbn, title, author, publisher, price);
        } catch (Exception e) {
            // To be logged
        }
    }
   public Collection getAllTitles() {
        Collection titles = new ArrayList();
        try {
            Context context = new InitialContext();
            BookLocalHome home = (BookLocalHome)context.lookup(BOOK_JNDI_NAME);
            Collection books = home.findAllBooks();
            for (Iterator it = books.iterator(); it.hasNext();) {
                BookLocal book = (BookLocal)it.next();
                titles.add(book.getTitle());
            }
        } catch (Exception e) {
            titles.add("例外が発生:" + e.toString());
        }
        return titles;
    }
    public String getCurrentTitle() {
        String title = null;
        try {
            Context context = new InitialContext();
            BookLocalHome home = (BookLocalHome)context.lookup(BOOK_JNDI_NAME);
            BookPK pk = new BookPK(ISBN);
            BookLocal book = home.findByPrimaryKey(pk);
            title = book.getTitle();
        } catch (Exception e) {
            title = "見つかりません:" + e.toString();
        }
        return title;
    }
    private static final String BOOK_JNDI_NAME = "java:comp/env/ejb/Book";
    private static final String ISBN = "4822281523";
    // ... 省略
} | 
まず、実装したJavaのソースコードをコンパイルします。
C:\ejbdev> javac -d dist src\jp\gr\java_conf\torutk\ejb\javareading\Assignment*.java C:\ejbdev>
Deploytoolを起動します。
[File]→[New]→[Enterprise Bean...]を選択して、EJB JARファイル作成ウィザードを起動します。
[Next >]をクリックし、Bean実装クラス、リモートインタフェース、ホームインタフェースクラスの指定を行います。
[Next >]をクリックし、Webサービスのエンドポイント指定を行います。
[Next >]をクリックして、[Finish]でウィザードを終了します。
”Book"エンティティBeanとセッションファサード対応した"Assignment"セッションBeanとを一つのEJB JARにまとめてもよいのですが、普通はこれらは別々に開発されます。エンティティBeanは、対象業務分野において共通に表われるオブジェクトを表現する場合が多く、複数のユースケースあるいはシステム間で共通に利用されます。一方セッションBeanは、ある特定の業務機能を処理するコントロールを表現する場合が多く、特定のユースケースにおいて設計されます。したがって、これらは別々なEJB JARとして組み立てておき、アプリケーションの組み立て時に統合されるようにすることが一般的です。
Deploytoolを起動し、配備記述子の設定を行います。
アプリケーションEARファイルを作成します。[File]→[New]→[Application...]を選択して、Application File Nameに assignmentApp.ear を指定します。
Deploytoolの左側ツリービューで、assignmentAppを選択した状態で、[File]→[Add to Application]→[Enterprise JavaBean JAR...]を選択します。以下の2つのEJB JARファイルを追加します。
セッションファサードとして作成した"Assignment"セッションBeanは、委譲先の"Book"エンティティBeanを検索する際、"java:comp/env/ejb/Book"というJNDI名を使用しています。このJNDI参照名を、実際の"Book"エンティティBeanに結びつける設定を行います。
Deploytoolの左側ツリービューで、assignmentJARの下のassignmentBeanを選択した状態で、右側画面の[EJB Ref's]タブをクリックします。[Add]ボタンをクリックすると、EJB参照の作成ができます。以下の設定を行います。
上記の指定内容が、EJB's Referenced in Codeに追加されます。
Deploytoolの左側ツリービューでassignmentAppを選択した状態で、[Tools]→[Deploy...]を実行します。
Deploytoolの左側ツリービューで、配備先のServers→マシン名を選択した状態で、右画面にあるassignmentAppを選択し、[Client JAR...]ボタンをクリックします。出力先ディレクトリを指定すると、assignmentAppClient.jarというファイルが生成されます。
書籍一覧を作成するコマンド、書籍一覧を表示するコマンド、そして現在の課題図書を表示するコマンド、の3つを機能として持たせています。
| public class SimpleClient {
    public SimpleClient(AssignmentHome aHome) {
        home = aHome;
    }
    public void createSomeBooks() throws Exception {
        Assignment ejb = home.create();
        ejb.createBook("4822281523", "EJBデザインパターン", "Floyd Marinescu", "日経BP", 2800);
        ejb.createBook("4894714361", "Effective Java", "Joshua Bloch", "ピアソン・エデュケーション", 2600);
        ejb.createBook("4881359185", "Javaスレッドプログラミング", "Doug Lea", "翔泳社", 4200);
        ejb.createBook("4894711877", "Javaの格言", "Nigel Warren", "ピアソン・エデュケーション", 2400);
    }
    public void printAllBooks() throws Exception {
        Assignment ejb = home.create();
        Collection titles = ejb.getAllTitles();
        System.out.println("書籍題名一覧は、" + titles.size() + "件が取得できました");
        for (Iterator it = titles.iterator(); it.hasNext();) {
            System.out.println(it.next());
        }
    }
    public void printCurrentAssignment() throws Exception {
        Assignment ejb = home.create();
        String title = ejb.getCurrentTitle();
        System.out.println("現在の課題図書は、" + title + " です。");
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage:java client.SimpleClient <command>");
            System.out.println("  command = create | all | current");
            System.out.println("  create  : 書籍を作成");
            System.out.println("  all     : 書籍一覧");
            System.out.println("  current : 現在の課題図書");
            System.exit(1);
        }
        try {
            Context initialContext = new InitialContext();
            Object homeRef = initialContext.lookup("AssignmentBean");
            AssignmentHome home = (AssignmentHome)PortableRemoteObject.narrow(homeRef, AssignmentHome.class);
            SimpleClient client = new SimpleClient(home);
            if ("create".equals(args[0])) {
                client.createSomeBooks();
            }
            if ("all".equals(args[0])) {
                client.printAllBooks();
            }
            if ("current".equals(args[0])) {
                client.printCurrentAssignment();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private AssignmentHome home;
 
} | 
コンパイルにあたっては、J2EE APIを含むj2ee.jarと、EJBコンポーネント配備時に生成したクライアント用JARファイルが必要となります。
E:\work>javac -d classes -classpath %J2EE_HOME%\lib\j2ee.jar;lib\assignmentAppClient.jar src\client\SimpleClient.java E:\work>
コマンドラインに"create"を指定して実行します。
E:\work>java -cp %J2EE_HOME%\lib\j2ee.jar;lib\assignmentAppClient.jar;classes client.SimpleClient create E:\work>
コンソール上は何も表示されませんが、データベース上に書籍が生成されます。データベース内容を見るには、PointBase付属のstartconsoleツールを使用すると便利です。次節にstartconsoleの使い方を述べます。
コマンドラインに"create"を指定して実行します。
E:\work>java -cp %J2EE_HOME%\lib\j2ee.jar;lib\assignmentAppClient.jar;classes client.SimpleClient all 書籍題名一覧は、4件が取得できました EJBデザインパターン Effective Java Javaスレッドプログラミング Javaの格言 E:\work>
E:\work>java -cp %J2EE_HOME%\lib\j2ee.jar;lib\assignmentAppClient.jar;classes client.SimpleClient current 現在の課題図書は、EJBデザインパターン です。 E:\work>
J2EE SDKでは、PointBaseを永続化に使用しています。
データベース内容を見るためのツールとして、startconsole.batが提供されています。これを実行します。
%J2EE_HOME%
     +------ pointbase
                 +------ tools
                           +------ serveroption
                                       +------ startconsole.bat
URLは、EJBアプリケーションが使用するものを指定する必要があります。JDBCリソース"jdbc/PointBase"で指定しているJDBC接続プール"PointBase Pool"の設定にあるDatabase Name "jdbc:pointbase:server://localhost:9092/sun-appserv-samples"がURLになります。
コンソールが立ち上がったら、左側ツリービューのSCHEMASをドリルダウンして、PBPUBLIC(ユーザ名として使用)のTABLESをドリルダウンすると、生成されているテーブルの中に、今回使用しているBOOKBEANが存在していることが分かります。右側上画面"Enter SQL Commands"において、テーブル内容を取り出すSELECT文を記述して実行してみると、右側下画面にSQL実行結果が表示されます。クライアント・プログラムをまだ実行していないならば、最初はまだテーブル内容が空なので、列名だけが表示されています。
クライアント・プログラム(createオプション)実行後にもう一度データベースのテーブルBOOKBEANの内容を見て見ます。[Enter SQL Commands]欄に残っている実行したいSQL文の行にカーソルを合わせて[Execute]ボタンを押せば再実行が簡単にできます。
EJBコンポーネントに対してCORBAクライアントからアクセスします。C++言語等で作成したアプリケーションからも、CORBAを介してEJBコンポーネントを利用することが可能になります。
J2SEのrmicコマンドを利用して、JavaのリモートインタフェースからCORBA IDLファイルを生成します。
E:\work>rmic -idl -noValueMethods -d idl -classpath lib\assignmentJARClient.jar;%J2EE_MOE%\lib\j2ee.jar jp.gr.java_conf.torutk.ejb.javareading.Assignment jp.gr.java_conf.torutk.ejb.javareading.AssignmentHome E:\work>
-noValueMethodsの指定は、引数および戻り値の型として基本型、配列、文字列型だけを使用するメソッドだけをIDL生成時の対象にします。これは複雑なIDLの生成を抑制し、クライアントの実装を簡単にします。シリアライズオブジェクトを対象にする必要がある場合、-noValueMethod指定を除きます。
以下のファイルが生成されます。
| idl
  +-- java
  |     +-- lang
  |           +-- Ex.idl
  |           +-- Exception.idl
  |           +-- Object.idl
  |           +-- Throwable.idl
  |           +-- ThrowableEx.idl
  +-- javax
  |     +-- ejb
  |           +--- CreateEx.idl
  |           +--- CreateException.idl
  |           +--- EJBHome.idl
  |           +--- EJBMetaData.idl
  |           +--- EJBObject.idl
  |           +--- Handle.idl
  |           +--- HomeHandle.idl
  |           +--- RemoveEx.idl
  |           +--- RemoveException.idl
  +-- jp
       +-- gr
           +-- java_conf
                 +-- torutk
                       +-- ejb
                             +-- javareading
                                   +-- Assignment.idl
                                   +-- AssignmentHome.idl | 
※ フリーのCORBA実装系では、TAOはIDLコンパイル不可。MICOはIDLから生成されたC++コードのコンパイル時にエラーとなります。うーむ・・・