[ 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で必要なものだけをインストールすればよいでしょう。

インストール(Windows)

Sun MicrosystemsのWebサイトからGet the SDKのリンクをたどってダウンロードします。2005年5月現在は1.4 2005Q1 UR1バージョンです。

Application Serverインストール

既に最新の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の画面

Application Server 情報

インストール(Linux)

Sun Java System Application Server Platform Edition 8 Update 1

ファイル名:sjsas_pe-8_0_0_01-linux.bin

Sun Java System Application Server Platform Edition 8 Update 1のSeparate Bundlesです。

Application Serverのインストール

コマンドライン環境からインストールすることができます。

rootでインストールすると、実行時にroot権限が必要になるので、Application Server専用のユーザ・アカウントを設けると良いでしょう。

インストール(Solaris 10 x86)

Application Serverのインストール

インストールには、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%----------------

J2EE SDKの構成

プログラム

サーバ系

起動順番は、先にデータベースを立ち上げ、続いてアプリケーションサーバを起動します。

(起動時にデータベースに接続に行くことはなさそうなので、順番は逆でもよさそうです)

PointBase

RDBMSを実行します。

Windows

メニューの[Start PointBase]を実行します。

Linux

コマンドラインから、pointbase起動用スクリプトを実行します。

torutk$ $J2EE_HOME/pointbase/tools/serveroption/startserver.sh
Server started, listening on port 9092, display level: 0 ...
>

アプリケーション・サーバ

J2EEのApplication Server(WebコンテナおよびEJBコンテナ)を実行します。

Windows

メニューの[Start Default Server]を実行します。

2行目のLog .... が出てからしばらくたって(数十秒〜数分)、Domain ... 以下の行が表示されます。少々気長に待って下さい。

Linux

コマンドラインから、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を生成して実行しているため。

BluePrintsを動かす

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

Adventure 1.0 in BluePrints

インストール

データベースサーバの起動

まずPointBaseを起動します。起動手順は前述のとおりです。

J2EEサーバの起動

続いて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にする理由もないので、多少はJ2EEの意義が見えるような、それでいて全体像の把握が易しい題材を考えてみます。

題材

多少データベースを使う必然性があって、かつシンプルな題材として、書籍管理システムを作ってみます。といっても単に書籍目録ではアプリケーションにならないので、ここでは「Java読書会」における課題図書を扱うアプリケーションを作成します。

要求メモ

とりあえず要求になりそうなことを羅列します。この中からどれかをピックアップしてサンプルを作成します。

まず、2番目の要求を実現するステートレスセッションBeanを作成してみます。

開発環境設定

ここではエディタ+素のJDKで基礎的な作成方法を会得します。基礎が分かれば、あとは便利な開発環境を好みでチョイスして使えばよいのです。基礎を知っていれば、チョイスすることが可能になります。

環境変数 設定例 備考
J2EE_HOME C:\java\j2ee1.4
PATH %J2EE_HOME%\bin 既存のPATHに追加
CLASSPATH %J2EE_HOME%\lib\j2ee.jar 既存のCLASSPATHに追加

EJBコンポーネントの作成方法

J2EEアプリケーションでは、EJBが中核になります。もちろんEJBなしにWeb層に自前でビジネスロジック層を付け加えて作成することもありますが、「専用クライアント」や「C++アプリケーション」からもアクセスするにはEJBが便利です。

EJBコンポーネントの開発手順は以下のようになります。

  1. リモートインタフェースの作成
  2. ローカルインタフェースの作成
  3. ホームインタフェースの作成
  4. ローカルホームインタフェースの作成
  5. Beanクラスの作成
  6. 配備記述子の作成*1
  7. 配備するためのアーカイブ作成
  8. 配備します

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

セッションBeanの作成

設計

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;
}

実装

Beanクラスの作成

ビジネスロジックを実装するクラスとなります。

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;
}

配備記述子の作成

エディタで作成したかったのですが、記述がエラーとなるので、アプリケーションサーバ付属のツールで作成するのがよいでしょう。ツールを使った手順は後述します。

エラーとなってしまう配備記述子(ejb-jar.xml)
<?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ベースのスキーマに変更になっているようです。以下の記事が参考になるでしょう。

J2EE SDK 1.4のデプロイツールで生成された配備記述子(ejb-jar.xml)
<?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のデプロイツールで生成されるベンダ固有配備記述子の例です。

J2EE SDK 1.4のデプロイツールで生成された配備記述子(sun-ejb-jar.xml)
<?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を単独で生成する
  2. 既存のJ2EEアプリケーションJAR(EAR)に追加する形で生成する
  3. 既存のEJB JARに追加する

ここでは、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クライアントの作成

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
コマンドラインのオプションでJNDIプロパティを指定する方法
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>
jdni.propertiesファイルをクラスパス指定ディレクトリに置く方法
jndi.properties
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を介して使用する設計手法(セッション・ファサード・パターン)を採用します。したがって、ローカルインタフェースのみ作成します。
エンティティ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

Beanクラスの作成

ローカルインタフェース設計時に定義した属性に対する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
注記1
ejbCreateメソッドの戻り値は、nullを返すようにしないといけない(CMP2.0)。また、戻り値の型は主キーと同じ。
注記2
ejbCreateとejbPostCreateは同じ引数で、かつホームインタフェースのcreateメソッドの引数と同じものとして定義する必要がある。ejbCreateだけ定義してejbPostCreateメソッドを定義し忘れたり引数が違っていると、下記のようなエラーが発生する。(エラーメッセージは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> 

配備記述子の作成(1) Wizardでの設定

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上で設定を続けます。

配備記述子の作成(2)

Deploytoolの左側ツリービューから、上述で生成したBookBeanを選択します。右側でEntityタブを選択すると、永続化関連の設定パネルが表示されます。

検索メソッドのEJB QL定義

[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の管理コンソールを使用して実施します。

  1. 管理コンソールの立ち上げ
    WWWブラウザにて、http://localhost:4848/asadminをアクセスします。下記のログイン画面が表示されます。
  1. JDBCリソースの設定
    左側ツリービューでJDBC→Connection Poolsを選択します。現在設定されているJDBCリソースの一覧が右画面に表示されます。新たなJDBCリソースを作成する場合は、[New...]をクリックして以下の指定を記述します。 ここで、jdbc/PointBaseの設定内容は以下の画面のようになっています。
  1. JDBC接続プールの設定
    左側ツリービューでJDBC→Connection Poolsを選択します。現在設定されているJDBC接続プールの一覧が右画面に表示されます。新たなJDBCリソースを作成する場合は、右側画面で[New...]をクリックして以下の指定を記述します。 ここで、"PointBase Pool"の内容は以下のとおりです。
    JDBC Connection Pool "PointBase Pool"設定内容
    設定項目 設定内容
    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
CMPリソース JNDI名の指定

永続化方法を指定します。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ファイル構成となります。

deploytoolによって生成されるファイル
配備記述子(ejb-jar.xml)
<?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>
配備記述子(sun-ejb-jar.xml)
<?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>
配備記述子(sun-cmp-mappings.xml)
<?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>
配備記述子(sun-ejb-jar.xml)
<?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"Assignment"の設計変更

エンティティ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

Bean実装クラス

セッションファサードは、クライアントからの要求を実際に処理を受け持つ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";
    // ... 省略
}

Assignment JARの作成

コンパイル

まず、実装したJavaのソースコードをコンパイルします。

C:\ejbdev> javac -d dist src\jp\gr\java_conf\torutk\ejb\javareading\Assignment*.java

C:\ejbdev> 

配備記述子の作成

Deploytoolを起動します。

EJB JARファイル作成(1) General Settings

[File]→[New]→[Enterprise Bean...]を選択して、EJB JARファイル作成ウィザードを起動します。

[Next >]をクリックし、Bean実装クラス、リモートインタフェース、ホームインタフェースクラスの指定を行います。

[Next >]をクリックし、Webサービスのエンドポイント指定を行います。

[Next >]をクリックして、[Finish]でウィザードを終了します。

EJBアプリケーションの作成と配備

”Book"エンティティBeanとセッションファサード対応した"Assignment"セッションBeanとを一つのEJB JARにまとめてもよいのですが、普通はこれらは別々に開発されます。エンティティBeanは、対象業務分野において共通に表われるオブジェクトを表現する場合が多く、複数のユースケースあるいはシステム間で共通に利用されます。一方セッションBeanは、ある特定の業務機能を処理するコントロールを表現する場合が多く、特定のユースケースにおいて設計されます。したがって、これらは別々なEJB JARとして組み立てておき、アプリケーションの組み立て時に統合されるようにすることが一般的です。

配備記述子の作成

Deploytoolを起動し、配備記述子の設定を行います。

アプリケーションEARファイル作成

アプリケーションEARファイルを作成します。[File]→[New]→[Application...]を選択して、Application File Nameに assignmentApp.ear を指定します。

アプリケーションEARファイルに、EJB JARファイルを追加

Deploytoolの左側ツリービューで、assignmentAppを選択した状態で、[File]→[Add to Application]→[Enterprise JavaBean JAR...]を選択します。以下の2つのEJB JARファイルを追加します。

EJB参照の設定

セッションファサードとして作成した"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に追加されます。

EJBアプリケーションの配備

Deploytoolの左側ツリービューでassignmentAppを選択した状態で、[Tools]→[Deploy...]を実行します。

EJBクライアント用JARの生成

Deploytoolの左側ツリービューで、配備先のServers→マシン名を選択した状態で、右画面にあるassignmentAppを選択し、[Client JAR...]ボタンをクリックします。出力先ディレクトリを指定すると、assignmentAppClient.jarというファイルが生成されます。

EJBクライアントの作成

専用クライアント

SimpleClientの改修

書籍一覧を作成するコマンド、書籍一覧を表示するコマンド、そして現在の課題図書を表示するコマンド、の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>

Web層(Servlet)

データベース内容

J2EE SDKでは、PointBaseを永続化に使用しています。

startconsole

データベース内容を見るためのツールとして、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]ボタンを押せば再実行が簡単にできます。

J2EEプログラミング・メモ

EJB

EJBプログラミングの規約

CORBAクライアント(C++)

CORBAクライアントの作成

EJBコンポーネントに対してCORBAクライアントからアクセスします。C++言語等で作成したアプリケーションからも、CORBAを介してEJBコンポーネントを利用することが可能になります。

CORBAクライアント作成手順

  1. EJBコンポーネントのIDL生成

リモートインタフェース・ホームインタフェースのIDL作成

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

C++への展開

※ フリーのCORBA実装系では、TAOはIDLコンパイル不可。MICOはIDLから生成されたC++コードのコンパイル時にエラーとなります。うーむ・・・