Session Bean
アプリケーションサーバーに対して単一クライアントとして振舞う。
複雑なビジネスロジックの入り口としての役割を果たす。
ある時点においてセッションビーンはクライアントに共有されない。
パーシステントではない。クライアントがいなくなると意味は持たない。
Stateless Session Beans
ステートレスセッションビーンはクライアントの状態にかかわる情報を
保持しない。ステートレスセッションビーンのメソッドの実行中には
そのインスタンス変数にクライアントのステートにかかわる情報を保持
することもあるが、そのメソッドが終わると意味は持たない。コンテナは
ステートレスセッションビーンをどのクライアントにもアサインできる。
ステートレスセッションビーンのみが Web Service を実装できる。
Stateful Session Beans
クライアントの状態に関する情報をインスタンス変数に持つことが出来る。
クライアントがビーンを削除したり、セッションが終わると状態はなくなる。
Entity Bean
永続化されるビジネスオブジェクトを表すビーン。通常リレーショナル
データベースに保持され、あるビーンはテーブル内の行に対応する。
セッションビーンとは次の様な点で異なる。
- 永続化される
- (複数のクライアントから)共有されることがある
- プライマリキーを持つ
- 他のエンティティビーンと参照関係を持つことがある
永続化には Bean Managed と Container Managed がある。
Message-Driven Bean
JMS Message リスナーとして振舞う。アプリケーションクライアント、
エンタープライズビーン、Web コンポーネントなどがメッセージを
送ることができる。
J2EE実習
Enterprise/Enterprise Application からプロジェクトを作成。
ConverterApp という名前にする。
デフォルトでは ConverterApp-EJBModule, ConverterApp-WebModule も
作成される
Session Bean の作成
ConverterApp-EJBModule で New -> Session Bean, 名前を Converter とする。
属性は stateless, remote とする。package は converter とする。
ConverterBean.java, ConverterRemote.java, ConverterRemoteBusiness.java,
ConverterRemoteHome.java が作成される。
- ConverterBean.java
- EJB の実装. implements SessionBean, ConverterRemoteBusiness
- SessionContext をプロパティとして持つ。
- ConverterRemote.java
- EJB のリモートインターフェイス。クライアントは
- このインターフェイスを介して EJB にアクセスする。
- extends EJBObject, ConverterRemoteBusiness . ビジネスメッソッドの
- インターフェイスは ConverterRemoteBusiness にまとめてある。
- ConverterRemoteBusiness.java
- ビジネスインターフェイス。最初は
- から。
- ConverterRemoteHome.java
- リモートホームインターフェイス。
- extends EJBHome . ConverterRemote create() throws CreateException, RemoteException;
- のみが定義されている。
Business method の追加
ConverterApp-EJBModule -> Enterprise Bean -> ConverterSB -> add ->
Business method
メッソッド名 dollarToYen, return type BigDecimal, parameter dollors:
BigDecimal とする。
メッソッド yenToEuro, return type BigDecimal. parameter yen: BigDecimal
も追加する。
ConverterBean.java に次のインスタンス変数を加える。
BigDecimal yenRate = new BigDecimal("121.6000");
BigDecimal euroRate = new BigDecimal("0.0077");
メソッドのボディを書き換える。
public BigDecimal dollarToYen(BigDecimal dollars) {
BigDecimal result = dollars.multiply(yenRate);
return result.setScale(2, BigDecimal.ROUND_UP);
}
public BigDecimal yenToEuro(BigDecimal yen) {
BigDecimal result = yen.multiply(euroRate);
return result.setScale(2, BigDecimal.ROUND_UP);
}
Web Client の作成
ConverterApp-WebModule->New->Servlet, name=ConverterServlet, package=
converter
Locating home interface
ConverterServlet のボディーで right-click -> Enterprise Resource->
Call Enterpriese Bean -> ConverterSB. lookupConverterBean が
自動生成される。
processRequest に EJB をアクセスするコードを追加
out.println("<h1><b><center>Converter</center></b></h1>");
out.println("<hr>");
out.println("<p>Enter an amount to convert:</p>");
out.println("<form method=\"get\">");
out.println("<input type=\"text\" name=\"amount\" size=\"25\">");
out.println("<br>");
out.println("<p>");
out.println("<input type=\"submit\" value=\"Submit\">");
out.println("<input type=\"reset\" value=\"Reset\">");
out.println("</form>");
String amount = request.getParameter("amount");
if ( amount != null && amount.length() > 0 ) {
try {
converter.ConverterRemote converter;
converter = lookupConverterBean();
java.math.BigDecimal d =
new java.math.BigDecimal(amount);
out.println("<p>");
out.println("<p>");
out.println(amount + " Dollars are "
+ converter.dollarToYen(d) + " Yen.");
out.println("<p>");
out.println(amount + " Yen are "
+ converter.yenToEuro(d) + " Euro.");
converter.remove();
} catch (Exception e){
out.println("Cannot lookup or execute EJB!");
}
}
Run
アプリケーションの相対 URI を /ConverterServlet に設定して
実行すると Servlet が表示され、変換も動作する。
ConverterApp/dist/converterapp.ear を JBoss 403 に deploy すると
そのまま動作する。
Exception
EJBException は RuntimeException にラップされる。このシステムエラーは
クライアントではハンドリングできない。
アプリケーション例外にはユーザーが定義した物と javax.ejb に属する
例外がある。これらは適切にハンドリングされなければならない。
システムエラーの場合にはトランザクションはロールバックされるが、
アプリケーション例外の場合には行われない。
JBoss deploy error
JNDI error
jndi.properties で解決
# Sample ResourceBundle properties file
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
java.naming.factory.url=org.jboss.naming:org.jnp.interfaces
java.sql.SQLException: Table not found in statement [...]
jboss.xml が存在していなかった
javax.ejb.EJBException: ejbCreate: Unable to connect to database.Could not dereference object
コネクション作成のための情報が不足している。
Sun AS の場合 setup/connection-pool-derby_net.sun-resource に設定が
ある。
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<jdbc-connection-pool connection-validation-method="auto-commit" datasource-classname="org.apache.derby.jdbc.ClientDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" max-pool-size="32" max-wait-time-in-millis="60000" name="derby_netConnectionPool" pool-resize-quantity="2" res-type="javax.sql.DataSource" steady-pool-size="8">
<property name="connectionAttributes" value=";create=true"/>
<property name="serverName" value="localhost"/>
<property name="PortNumber" value="1527"/>
<property name="DatabaseName" value="sun-appserv-samples"/>
<property name="User" value="APP"/>
<property name="Password" value="APP"/>
</jdbc-connection-pool>
</resources>
JBoss のデータソース設定方法を確認...
ejb-jar.xml に書くデータ参照はあくまで参照名まで。参照名の定義は
サーバー固有のファイルに書くことになる。サーバー毎に柔軟に Connection
Pool を作り込めるためと考えれば納得できないこともない。
ejb-jar.xml 例:
...
<entity>
<display-name>SavingsAccountEB</display-name>
<ejb-name>SavingsAccountBean</ejb-name>
<home>bank.SavingsAccountRemoteHome</home>
<remote>bank.SavingsAccountRemote</remote>
<ejb-class>bank.SavingsAccountBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>false</reentrant>
<resource-ref>
<description>jdbc:derby://localhost:1527/sun-appserv-samples;create=true [APP の APP]</description>
<res-ref-name>jdbc/myDatabase</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</entity>
jboss.xml も JDBC driver name, url までは持っていない。これらは
サーバーのリソースということだ。jca の定義ファイルを適切に直して
deploy, driver クラスを用意して再起動する必要がある。
driver name, url, これらを基にしたコネクションプールの管理は
Sun AS でもサーバー管理下にあるのは同じ事。紛らわしいのはその
定義のコピーが NetBeans project 内に作られること。それは情報の
コピーということであり、その project ないの情報がコネクションプール
作成に直接影響するわけではないだろう。
jboss.xml:
<jboss>
<enterprise-beans>
<entity>
<ejb-name>SavingsAccountBean</ejb-name>
<jndi-name>ejb/SavingsAccountBean</jndi-name>
<resource-ref>
<res-ref-name>jdbc/myDatabase</res-ref-name>
<resource-name>DataSource1</resource-name>
</resource-ref>
</entity>
</enterprise-beans>
<resource-managers>
<resource-manager>
<res-name>DataSource1</res-name>
<res-jndi-name>java:/MySqlDS</res-jndi-name>
</resource-manager>
</resource-managers>
</jboss>
この例では java:/MySqlDS がサーバーのデータソースの名前。
derby-ds.xml を deploy にコピーして Embeded 用の設定をクライアント
用に書き換える。Embeded 用だとソケット経由の URL を認識しないため。
derbyclient.jar を server/<type>/lib にコピーして、再起動。
server/<type>/lib 以下は hot-deploy ではないようだ。
これらを行っても使えるようにならなかった。デバッグ文を入れると
JNDI lookup で失敗しているようだ。Sun の BMP の SavingsAccount
はサンプルのためか、このあたりの細かいログは掃いてくれないため、
デバッグ文を入れて初めて(予想はしていたが..)判明。
server.log をよく見ると
Derby のデータソースが一旦作られたが、JMX にマッピングをとろうと
する段階で問題があったのか削除されているように見える。Embeded 用の
ドライバを想定しているコードがあるのかも知れない。JMX は便利そうで
実はあまりそうでもないので JMX に結びつける文を derby-ds.xml
からはずす。
再度挑戦、しかし今度はドライバをインスタンスかしている途中で
ClassCastException がおきている。Derby JDBC driver で
実装し忘れているインターフェイスがあるのか、基底クラスの
互換問題かむづかしそう。jboss のサイトでその例外を投げている
メソッドを見てみるが、キャストは (Property) ぐらいにしかやって
ない。これもドライバのつくりの問題を感じさせる。ただ、型が
シンプルなのでそのメソッドをデバッグすれば何かわかるかも知れないが、
JBoss のビルド以前途中で挫折した記憶があり、一旦保留。
ここまでの過程で DB を変えるために必要なステップもおのずと明らかに
なってきた。複数の実行環境から参照できるように MySQL を使うことに
した。ユーザー、DB,Table の作り方を復習しながら app server 用の
テーブルとユーザーを用意する。テーブル作成の SQL は若干の手直しが
必要だった。'constraint <sym>' の部分がエラーになってしまうためだ。
これをとってテーブルは作成できた。
あとは mysql-ds.xml を書き換え deploy にコピー。 Connector/J の
jar ファイルを server/<type>/lib にコピー、jboss.xml の
java:/<DB ID> を MySQL 用に直し、再起動。
今度は動いた。半日以上棒に振った気がするが、よい勉強になったとも
いえる。(最近は直ぐに忘れてしまうのだが...)
SavingsAccount の ejbCreate では balance が負の場合には
CreateException を throw するが、それ以外の場合には insertRow()
で DB 格納処理に進む。すでに存在するユーザーID に対してこれを
行うと EJBException になる。分類上はこれはシステムエラーということに
なり、コンテナはロールバックするし、クライアントには RemoteException,
local には EJBException となる。では、ここで ID (primary key) の
有無を調べてアプリケーションエラーにしてよいのだろうか?おそらく、
そうしたほうがクライアントは書きやすくなる。しかし、ひとつ例外が
おきる条件が増えることでもある。これはもっとサンプルや、資料を調べて
見ないと厳密な事は言えなそう。
最終更新:2006年09月01日 19:02