作者:自由的豬 制作整理:
左岸網(wǎng)絡(luò)
http://www.leftworld.net第6章 CMP的例子
Dale Green 著
Iceshape Zeng 譯
CMP實(shí)體Bean技術(shù)給開發(fā)者帶來了很多重要的好處。首先,EJB容器處理了所有數(shù)據(jù)庫(kù)的訪問操作。其次,容器管理了實(shí)體Bean之間的關(guān)系。因?yàn)檫@些服務(wù),你不需要為數(shù)據(jù)庫(kù)訪問而編寫任何代碼,而只用在部署描述符里指定配置。這種方法不僅可以節(jié)省開發(fā)者的時(shí)間,更重要的是它使企業(yè)Bean在訪問不同的數(shù)據(jù)庫(kù)時(shí)有更好的可移植性。
本章將重點(diǎn)介紹CMP實(shí)現(xiàn)的實(shí)體Bean應(yīng)用程序RosterApp這個(gè)例子的代碼和部署描述符設(shè)置。如果你對(duì)本章提到的術(shù)語和概念不太熟悉,請(qǐng)參考第4章企業(yè)Bean的CMP部分。
本章內(nèi)容:
RosterApp應(yīng)用概述
PlayerEJB代碼分析
實(shí)體Bean類
Local Home接口
Local接口
RosterApp設(shè)置說明
RoseterApp應(yīng)用程序設(shè)置
RosterClient客戶端設(shè)置
RosterJAR設(shè)置
TeamJAR設(shè)置
RosterApp中的方法調(diào)用
創(chuàng)建一個(gè)Player實(shí)體
將Player加入一個(gè)Team實(shí)體
刪除一個(gè)Player
從Team中刪除一個(gè)Player
查詢一個(gè)Team中所有的Player
得到Team中所有Player的副本
查詢特定位置的Player
查詢一個(gè)Player參加的Sports
運(yùn)行RosterApp應(yīng)用程序
啟動(dòng)用到的程序
部署
運(yùn)行客戶端
用deploytool工具部署CMP實(shí)現(xiàn)的實(shí)體Bean
指定企業(yè)Bean類型
選擇持久性字段和抽象模式名
為查找方法和Select方法編寫EJB QL查詢
生成SQL語句和指定表創(chuàng)建機(jī)制
設(shè)置數(shù)據(jù)庫(kù)的JNDI名,用戶名和密碼
定義關(guān)系
CMP主鍵
主鍵類
實(shí)體Bean的主鍵
產(chǎn)生主鍵值
一、RosterApp應(yīng)用概述
RosterApp應(yīng)用程序維護(hù)運(yùn)動(dòng)社團(tuán)中運(yùn)動(dòng)員分組的花名冊(cè)。它有五個(gè)組成部分,RosterAppClient是通過Remote接口訪問會(huì)話Bean RosterEJB的J2EE客戶端,RosterEJB通過Local接口訪問三個(gè)實(shí)體Bean:PlayerEJB,TeamEJB和LeagueEJB。
這些實(shí)體Bean用容器管理的持久性和關(guān)系。TeamEJB和PlayerEJB之間是雙向的多對(duì)多關(guān)系。雙向關(guān)系中每一個(gè)Bean都有一個(gè)關(guān)系字段來確定相關(guān)聯(lián)另一個(gè)Bean實(shí)例。多對(duì)多關(guān)系是指:可以參加多個(gè)運(yùn)動(dòng)項(xiàng)目的運(yùn)動(dòng)員(Player)可以加入多個(gè)組(team),而每個(gè)組又有多個(gè)運(yùn)動(dòng)員。LeagueEJB和TeamEJB之間是一對(duì)多的雙向關(guān)系:一個(gè)社團(tuán)可以有多個(gè)組,而一個(gè)組只能屬于一個(gè)社團(tuán)。
圖6-1描述了該應(yīng)用程序各組件和它們之間的關(guān)系。虛線箭頭表示通過調(diào)用JNDI lookup方法的訪問。
圖 6-1 RosterApp應(yīng)用程序
二、layerEJB代碼分析
PlayerEJB表示運(yùn)動(dòng)社團(tuán)中的運(yùn)動(dòng)員實(shí)體。本例中,它需要以下三各個(gè)類:
1. 實(shí)體Bean類(PlayerBean)
2. Local Home接口(LocalPlayerHome)
3. Local接口(LocalPlayer)
你可以在本例的源代碼文件存放目錄j2eetutorial/examples/src/ejb/cmproster中找到以上代碼的源文件,要編譯這些源文件,在命令方式下進(jìn)入j2eetutorial/examples目錄,執(zhí)行antcmproster命令。RosterApp.ear的樣本文件存放在j2eetutorial/examples/ears目錄下。
實(shí)體Bean類
CMP實(shí)現(xiàn)的實(shí)體Bean必須符合CMP的語法規(guī)則。首先Bean類必須定義為公有(public)和抽象(abstract)的。其次要實(shí)現(xiàn)一下內(nèi)容:
1. EntityBean接口
2. 零對(duì)或多對(duì)ejbCreate和ejbPostCreate方法
3. 持久性字段和關(guān)系字段的get和set方法定義為abstract
4. 所有的select方法定義為abstract
5. 商業(yè)方法
CMP的實(shí)體Bean類不能實(shí)現(xiàn)如下方法
1. 查找方法
2. finalize方法
CMP和BMP實(shí)現(xiàn)實(shí)體Bean的代碼比較
因?yàn)镃MP不需要編寫數(shù)據(jù)庫(kù)訪問的代碼,所以CMP實(shí)現(xiàn)的實(shí)體Bean比BMP實(shí)現(xiàn)的實(shí)體Bean的代碼少得多。例如本章中討論的PlayerBean.java源文件要比第5章的代碼文件SavingsAccountBean.java小得多。下表比較了兩種不同類型實(shí)體Bean實(shí)現(xiàn)代碼的不同:
表6-1兩種持久性機(jī)制的編碼比較
不同點(diǎn)
CMP
BMP
企業(yè)Bean類定義
抽象
非抽象
數(shù)據(jù)庫(kù)訪問代碼
由工具產(chǎn)生
開發(fā)者編碼
持久性狀態(tài)
虛擬持久字段表示
代碼中的實(shí)例變量表示
持久性字段和關(guān)系字段的訪問方法
必須
不是必須
findByPrimaryKey方法
由容器處理
開發(fā)者編碼
其他查找方法
容器根據(jù)開發(fā)者定義的EJB QL查詢自動(dòng)處理
開發(fā)者編碼
select方法
容器處理
不需要
ejbCreate方法的返回值
應(yīng)該為null
必須是主鍵類
注意:對(duì)于兩種持久性機(jī)制,商業(yè)方法和Home方法的實(shí)現(xiàn)規(guī)則都是一樣的。參考第5章的商業(yè)方法和Home方法兩節(jié)。
訪問(get和set)方法
CMP實(shí)現(xiàn)的實(shí)體Bean中持久性字段和關(guān)系字段都是虛擬的,你不能把它們?cè)贐ean類中寫成實(shí)例變量,而應(yīng)該在部署描述符里列出它們。要訪問這些字段,你需要在實(shí)體Bean類中定義抽象的get和set方法。
持久性字段的訪問方法
EJB容器根據(jù)部署描述符信息自動(dòng)為持久性字段實(shí)現(xiàn)存儲(chǔ)到數(shù)據(jù)庫(kù)和從數(shù)據(jù)庫(kù)讀取操作。(具體的操作過程可能是在執(zhí)行部署的同時(shí),部署工具就根據(jù)部署描述符的信息生成了對(duì)應(yīng)的數(shù)據(jù)庫(kù)訪問代碼)本例中PlayerEJB的部署描述符中定義了以下持久性字段:
☆ playerId(primary key)
☆ name
☆ position
☆ salary
PlayerBean類中以上字段的訪問方法定義如下:
public abstract String getPlayerId();
public abstract void setPlayerId(String id);
public abstract String getName();
public abstract void setName(String name);
public abstract String getPosition();
public abstract void setPosition(String position);
public abstract double getSalary();
public abstract void setSalary(double salary);
訪問方法名以get或者set開頭,后跟頭字母大寫的對(duì)應(yīng)持久性字段名或者關(guān)系字段名。例如字段salary的訪問方法命名為:getSalary和setSalary。這種命名約定和JavaBean組件的相同。
關(guān)系字段的訪問方法
在RosterApp應(yīng)用程序中,因?yàn)橐粋€(gè)運(yùn)動(dòng)員可以屬于多個(gè)組,一個(gè)PlayerEJB實(shí)例可以關(guān)聯(lián)多個(gè)TeamEJB實(shí)例。為了說明這個(gè)關(guān)系,部署描述符中定義了一個(gè)名叫teams的關(guān)系字段。在PlayerBean類中,teams的訪問方法定義如下:
public abstract Collection getTeams();
public abstract void setTeams(Collection teams);
select方法
select方法和查找方法有很多相同的地方:
☆ select方法可以返回Local或者Remote接口或者它們之一的集合
☆ select方法訪問數(shù)據(jù)庫(kù)
☆ 部署描述符為每個(gè)select方法指定EJB QL查詢
☆ 實(shí)體Bean類并不實(shí)現(xiàn)select方法(只是聲明或者說定義)
當(dāng)然,select方法和查找方法還有一些顯著的區(qū)別:
1. 一個(gè)查找方法可以返回相關(guān)聯(lián)實(shí)體Bean的一個(gè)持久性字段或者字段的集合。而查找方法值可以返回所屬實(shí)體Bean的Local或者Remote接口或者它們之一的集合。
2. 因?yàn)閟elect方法在Local或者Remote接口中沒有被定義,所以它們不能被客戶端訪問,而只能被所屬實(shí)體Bean中實(shí)現(xiàn)的方法調(diào)用(用點(diǎn)像類的私有方法)。select方法通常被商業(yè)方法調(diào)用。
3. select方法在實(shí)體Bean類里定義。BMP的查找方法也在實(shí)體Bean類中定義,但是CMP的查找方法不在實(shí)體Bean類里定義。CMP中的查找方法在Home或者Local Home接口中定義,在部署描述符中定義對(duì)應(yīng)的EJB QL查詢。
PlayerBean類定義了下面這些select方法:
public abstract Collection ejbSelectLeagues(LocalPlayer player)
throws FinderException;
public abstract Collection ejbSelectSports(LocalPlayer player)
throws FinderException;
select的方法簽名規(guī)則:
1. 方法名要以ejbSelect開頭
2. 訪問修飾符必須是public
3. 方法必須聲明為抽象的
4. throws子句必須包括javax.ejb.FinderException
商業(yè)方法
因?yàn)榭蛻舳瞬荒苷{(diào)用select方法,所以PlayerBean類將它們封裝(wraps)在商業(yè)方法getLeagues和getSports中:
public Collection getLeagues() throws FinderException {
LocalPlayer player = (team.LocalPlayer)context.getEJBLocalObject();
return ejbSelectLeagues(player);
}
public Collection getSports() throws FinderException {
LocalPlayer player = (team.LocalPlayer)context.getEJBLocalObject();
return ejbSelectSports(player);
}
實(shí)體Bean方法
因?yàn)樘幚鞢MP實(shí)體Bean的持久性,PlayerBean的生命周期方法都接近于空方法了。ejbCreate方法將參數(shù)賦值給持久性字段以初始化實(shí)體Bean實(shí)例。ejbCreate方法調(diào)用結(jié)束后,容器向數(shù)據(jù)庫(kù)對(duì)應(yīng)表中插入一行。ejbCreate方法實(shí)現(xiàn):
public String ejbCreate (String id, String name,
String position, double salary) throws CreateException {
setPlayerId(id);
setName(name);
setPosition(position);
setSalary(salary);
return null;
}
ejbPostCreate方法必須和對(duì)應(yīng)的ejbCreate方法有相同的參數(shù)簽名和返回值。如果你想在初始化Bean實(shí)例時(shí)設(shè)置關(guān)系字段值,可以實(shí)現(xiàn)ejbPostMethod方法,而不可以在ejbCreate方法中設(shè)置關(guān)系字段值。
除了一個(gè)調(diào)試語句,PlayerBean的ejbRemove方法就是一個(gè)空方法了。容器在刪除數(shù)據(jù)庫(kù)表中對(duì)應(yīng)行之前調(diào)用ejbRemove方法。
容器自動(dòng)同步實(shí)體Bean狀態(tài)和數(shù)據(jù)庫(kù)數(shù)據(jù)。容器從數(shù)據(jù)庫(kù)中讀取實(shí)體狀態(tài)后調(diào)用ejbLoad方法,同樣的方式,在將實(shí)體狀態(tài)存入數(shù)據(jù)庫(kù)前調(diào)用ejbStore方法。(這句話有些費(fèi)解,因?yàn)樵贐MP中數(shù)據(jù)庫(kù)的同步是在ejbLoad和ejbStore這兩個(gè)方法里實(shí)現(xiàn)的,難道部署工具或者容器為CMP生成的實(shí)現(xiàn)子類不使用這兩個(gè)方法來實(shí)現(xiàn)數(shù)據(jù)庫(kù)同步的?)
Local Home接口
Local Home接口定義本地客戶端調(diào)用的create方法、查找方法和Home方法。
create方法的語法規(guī)則如下:
1. 方法名以create開頭
2. 和對(duì)應(yīng)的實(shí)體Bean類里定義的ejbCreate方法有相同的參數(shù)簽名
3. 返回實(shí)體Bean的Local接口
4. throws子句包括對(duì)應(yīng)ejbCreate方法throws子句中出現(xiàn)的所有異常加上javax.ejb.CreateException異常
查找方法的規(guī)則:
1. 方法名以find開頭
2. 返回實(shí)體Bean的Local接口或它的集合
3. throws子句包括javax.ejb.FinderException異常
4. 必須定義findByPrimaryKey方法
下面是LocalPlayerHome的部分代碼:
package team;
import java.util.*;
import javax.ejb.*;
public interface LocalPlayerHome extends EJBLocalHome {
public LocalPlayer create (String id, String name,
String position, double salary)
throws CreateException;
public LocalPlayer findByPrimaryKey (String id)
throws FinderException;
public Collection findByPosition(String position)
throws FinderException;
...
public Collection findByLeague(LocalLeague league)
throws FinderException;
...
}
Local接口
該接口定義了本地客戶端調(diào)用的商業(yè)方法和字段訪問方法。PlayerBean類實(shí)現(xiàn)了兩個(gè)商業(yè)方法getLeagues和getSports,同時(shí)還為持久性字段和關(guān)系字段定義了很多get和set訪問方法。但是客戶端不能調(diào)用set方法,因?yàn)長(zhǎng)ocalPlayer接口中沒有定義這些set方法。但是get方法客戶端可以訪問。下面是LocalPlayer的實(shí)現(xiàn)代碼:
package team;
import java.util.*;
import javax.ejb.*;
public interface LocalPlayer extends EJBLocalObject {
public String getPlayerId();
public String getName();
public String getPosition();
public double getSalary();
public Collection getTeams();
public Collection getLeagues() throws FinderException;
public Collection getSports() throws FinderException;
}
三、RosterApp配置說明
本節(jié)將引導(dǎo)你為CMP實(shí)現(xiàn)的實(shí)體Bean配置部署描述符。在這個(gè)過程中,還將討論deplouytool工具中出現(xiàn)的重要的選項(xiàng)頁和對(duì)話框。
請(qǐng)先運(yùn)行deploytool并打開j2eetutorial/examples/ears目錄下的RosterApp.ear文件。
RosterApp應(yīng)用程序配置
在屬性視圖中選中RosterApp節(jié)點(diǎn)以查看應(yīng)用程序的部署信息。
General頁(RosterApp)
Contents域顯示了RosterApp.ear中包含的文件,包括兩個(gè)EJB JAR文件(team-ejb.jar和roster-ejb.jar)和J2EE應(yīng)用程序客戶端JAR文件(roster-ac.jar)。如圖6-2:
圖 6-2 RosterApp 的General 頁
JNDI Names頁(RosterApp)
Application表列出了RosterApp應(yīng)用程序中的企業(yè)Bean的JNDI名。
References表有兩個(gè)條目,EJB Ref條目為RosterClient客戶端映射RosterEJB會(huì)話Bean的引用名(ejb/SimpleRoster)。Resource條目指定TeamJAR模塊中的實(shí)體Bean訪問的數(shù)據(jù)庫(kù)的JNDI名。
RosterClient客戶端配置
展開RosterApp節(jié)點(diǎn),選中RosterClient節(jié)點(diǎn),將顯示客戶端配置信息。
JAR File頁(RosterClient)
Contents域顯示了roster-ac.jar包含的文件:兩個(gè)XML文件(部署描述符文件)和一個(gè)類文件(RosterClient.class)。
EJB Refs頁(RosterCliet)
RosterClient客戶端訪問一個(gè)企業(yè)Bean:RosterEJB會(huì)話Bean。因?yàn)槭沁h(yuǎn)程訪問,Interfaces列選擇Remote,Local/Remote列填寫會(huì)話Bean的Remote接口(roster.Roster)。
RosterJAR的設(shè)置
在樹視圖中選中RosterJAR節(jié)點(diǎn)。該JAR文件包含RosterEJB會(huì)話Bean。
General頁(RosterJAR)
Contents域列出了三個(gè)包:roster包包含RosterEJB需要的類文件——會(huì)話Bean類,Remote接口和Home接口;team包包含RosterEJB要訪問的實(shí)體Bean的Local接口;util包里是應(yīng)用程序用到的一些實(shí)用類(utility classes)。
RosterEJB
展開RosterJAR節(jié)點(diǎn)點(diǎn)選RosterEJB節(jié)點(diǎn)。
General頁(RosterEJB)
本例中General選項(xiàng)頁顯示RosterEJB是一個(gè)遠(yuǎn)程訪問的有狀態(tài)會(huì)話Bean。因?yàn)樗恢С直镜卦L問,所以Local Interface域?yàn)榭铡?div style="height:15px;">
EJB Refs頁(RosterEJB)
RosterEJB會(huì)話Bean訪問三個(gè)實(shí)體Bean:PlayerEJB、TeamEJB和LeagueEJB。因?yàn)檫@些訪問都是本地訪問,這些引用條目中的Interfaces列中都定為L(zhǎng)ocal,Home Interface列顯示的是這些實(shí)體Bean的Local Home接口。Local/Remote Interfaces列顯示的是這些實(shí)體Bean的Local接口。
選中表中的一行,可以查看運(yùn)行時(shí)部署信息。例如當(dāng)你選擇Coded Name列為ejb/SimpleLeague的一行時(shí),LeagueEJB就顯示在Enterprise Bean Name域里。Enterpri Bean Name域需要設(shè)置成被引用的企業(yè)Bean的名字(在左邊樹視圖中顯示的名字)。
TeamJAR配置
點(diǎn)選樹視圖中的TeamJAR節(jié)點(diǎn)。該JAR文件包含三個(gè)相互關(guān)聯(lián)的實(shí)體Bean:LeagueEJB、TeamEJB和PlayerEJB。
General頁(TeamJAR)
Contents域顯示了該JAR文件中的兩個(gè)包:team和util。team包包含三個(gè)實(shí)體Bean的類文件、Local接口文件和Local Home接口文件。Util包就不再介紹了。
Relationships頁(TeamJAR)
如圖6-3,CMP實(shí)體Bean之間的關(guān)系在這個(gè)選項(xiàng)頁中定義。
圖 6-3 TeamJAR的Relationships頁
Container Managed Relationships表中定義了兩個(gè)關(guān)系:TeamEJB-PlayerEJB和LeagueEJB-TeamEJB。在TeamEJB-PlayerEJB關(guān)系中,TeamEJB被當(dāng)作EJB A而PlayerEJB被當(dāng)作EJB B(當(dāng)然你也可以交換它們的位置,這并不影響關(guān)系本身)。
關(guān)系編輯對(duì)話框(TeamJAR)
在上圖所示的Relationship頁中選擇表中的一行點(diǎn)擊Edit按鈕,就會(huì)彈出關(guān)系編輯對(duì)話框。(當(dāng)然Add按鈕也可以,只不過不需要選擇已有關(guān)系,因?yàn)?#8230;…)。如圖6-4是TeamEJB-PlayerEJB關(guān)系的編輯對(duì)話框,下面詳細(xì)介紹它們的關(guān)系。
TeamEJB-PlayerEJB關(guān)系
Multiplicity組合框提供四種可選的關(guān)系類型(因?yàn)檫@里是單向從A到B,所以一對(duì)多和多對(duì)一是兩種類型,其實(shí)可以在下面的EJB組合框中調(diào)換關(guān)系雙方的位置而讓它們合成一種)。TeamEJB-PlayerEJB是多對(duì)多關(guān)系(many to many(*:*)選項(xiàng))。
Enterprise Bean A框里描述了TeamEJB在關(guān)系中的信息。Field Referencing Bean B組合框里選擇了TeamEJB中的關(guān)系字段(players),這個(gè)字段對(duì)應(yīng)TeamBean.java文件中定義的以下兩個(gè)訪問方法:
public abstract Collection getPlayers();
public abstract void setPlayers(Collection players);
圖 6-4TeamJAR的關(guān)系編輯對(duì)話框
FieldType組合框選擇的是java.util.Collection以匹配為訪問方法中player字段的類型。因?yàn)樵趯?duì)TeamEJB在關(guān)系中PlayerEJB是“多”的一方(在多對(duì)多關(guān)系中雙方都是“多”),所以player字段的類型是多值對(duì)象(集合)。
TeamEJB-PlayerEJB的關(guān)系是雙向的——關(guān)系的雙方都有一個(gè)關(guān)系字段標(biāo)志關(guān)聯(lián)的實(shí)體Bean的實(shí)例。如果關(guān)系不是雙向的,沒有關(guān)系字段的一方在Field Referenceing組合框中選擇<none>。
LeagueEJB-TeamEJB關(guān)系
在關(guān)系編輯對(duì)話框里,LeagueEJB-TeamEJB關(guān)系的Multiplicity組合框選擇的是One to Many(因?yàn)橐粋€(gè)社團(tuán)可以有多個(gè)組,而且這里選定后,Enterprise Bean A就只能是LeagueEJB了)。
LeagueEJB中用關(guān)系字段teams來表示該社團(tuán)里所有的組。因?yàn)門eamEJB是關(guān)系中“多”的一方,所以teams字段是一個(gè)集合。而LeagueEJB是“一”的一方,所以TeamEJB中l(wèi)eague是單個(gè)的LocalLeaguer類型對(duì)象。TeamBean.java通過如下訪問方法定義關(guān)系字段league:
public abstract LocalLeague getLeague();
public abstract void setLeague(LocalLeague players);
在LeagueEJB-TeamEJB關(guān)系中定義了關(guān)聯(lián)刪除,TeamEJB中Delete When A Is Deleted符選框被選中。這樣當(dāng)一個(gè)LeagueEJB實(shí)例被刪除時(shí),與它相關(guān)聯(lián)的TeamEJB實(shí)例也將自動(dòng)地被刪除,這就是關(guān)聯(lián)刪除。LeagueEJB中相同的符選框并沒有選中,不能刪除一個(gè)組就刪除整個(gè)社團(tuán),因?yàn)檫€有其他組在社團(tuán)中。一般在關(guān)系中“多”的一方才會(huì)被關(guān)聯(lián)刪除,另一方不會(huì)被自動(dòng)刪除。
PlayerEJB
在樹視圖中選擇PlayerEJB節(jié)點(diǎn)(TeamJAR的字節(jié)點(diǎn)之一)。
General頁(PlayerEJB)
這一頁顯示企業(yè)Bean類和EJB接口。因?yàn)镻layerEJB是容器管理關(guān)系的靶子,所以它有本地接口(Local和Local Home接口),同時(shí)它不需要允許遠(yuǎn)程訪問,所以沒有遠(yuǎn)程接口(Remote和Home接口)。
Entity頁(PlayerEJB)
如圖6-5,在這一頁上面的單選按鈕組定義企業(yè)實(shí)體Bean的持久性類型。為PlayerEJB選定的是container-managed persistence(2.0),就是CMP2.0。因?yàn)镃MP1.0不提供容器管理的關(guān)系服務(wù),所以不推薦使用。(這些版本號(hào)是EJB規(guī)范的版本號(hào),而不是J2EE SDK的版本號(hào))
Fields To Be Persisted列表框列出了PlayerBean.java中定義了訪問方法的所有持久性字段和關(guān)系字段。持久性字段前面的選擇框必須被選中,而關(guān)系字段不能不選中。就是說關(guān)系字段(外鍵)不在本實(shí)體Bean中被持久化(這里或許是因?yàn)閿?shù)據(jù)庫(kù)表本身是用參照約束實(shí)現(xiàn)的外鍵關(guān)聯(lián),所以關(guān)系字段不需要在這里持久化,如果數(shù)據(jù)表關(guān)系是用應(yīng)用程序編碼檢查來實(shí)現(xiàn),那么這里就應(yīng)該讓關(guān)系字段也被持久化,但是這時(shí)這個(gè)字段還是不是關(guān)系字段呢?)。PlayerEJB實(shí)體Bean的關(guān)系字段只有一個(gè):teams。
abstract schema name是Player,這個(gè)名字代表PlayerEJB實(shí)體Bean的所有持久性字段和關(guān)系字段(抽象模式名標(biāo)志一個(gè)定義持久性字段和關(guān)系字段的抽象模式)。這個(gè)抽象模式名將在為PlayerEJB定義的EJB QL 查詢中被引用。EJB QL將在第8章論述。
圖 6-5PlayerEJB的Entity頁
Finder/Select Methods對(duì)話框(PlayerEJB)
在Entity頁點(diǎn)擊Finder/Select Methods按鈕會(huì)打開Finder/Select Methods對(duì)話框,如圖6-6。你可以在這個(gè)對(duì)話框里查看和編輯為CMP實(shí)體Bean的查找和Select方法寫的EJB QL查詢。
圖 6-6 Finder/Select Methods 對(duì)話框
Entity Deployment Settings對(duì)話框(PlayerEJB)
在Entity頁點(diǎn)擊Deployment Settings按鈕打開該對(duì)話框,該對(duì)話框用來設(shè)置CMP實(shí)體Bean的運(yùn)行時(shí)配置信息。這些信息是J2EE SDK特有的,其他的J2EE平臺(tái)可能有些不同。在J2EE SDK中,實(shí)體Bean的持久性字段存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中,在Database Table框中你可以確定是否讓服務(wù)器自動(dòng)創(chuàng)建和刪除數(shù)據(jù)庫(kù)中的表。如果你想把數(shù)據(jù)保存在數(shù)據(jù)庫(kù)中,就不選中Delete table on undeploy復(fù)選框,否則當(dāng)你從服務(wù)器卸載實(shí)體Bean時(shí),表將被刪除。
J2EE服務(wù)器通過SQL語句訪問數(shù)據(jù)庫(kù),用CMP實(shí)現(xiàn)實(shí)體Bean時(shí)你不需要自己編寫這些SQL語句,點(diǎn)擊General Default SQL按鈕deploytool工具會(huì)自動(dòng)生成這些語句。然后在Method表格里選中任意一個(gè)方法,會(huì)在右邊的SQL Query域里看到為這個(gè)方法生成的SQL語句,你可以在這里修改這些生成的語句。
對(duì)查找和Select方法,對(duì)應(yīng)的EJB QL查詢也會(huì)顯示在下面的EJB QL Query域里。在你點(diǎn)擊General Default SQL按鈕時(shí),deploytool把EJB QL查詢轉(zhuǎn)換成SQL語句,如果你修改了EJB QL查詢,你應(yīng)該讓deploytool重新生成SQL語句。
點(diǎn)擊Container Methods單選按鈕,可以看到為容器管理方法生成的SQL語句。如createTable方法的創(chuàng)建表SQL語句,當(dāng)然還有刪除表的SQL語句。
當(dāng)容器創(chuàng)建一個(gè)PlayerEJB實(shí)例時(shí),它生成一條插入數(shù)據(jù)(INSERT)的SQL語句,要查看這條語句,在Method表各種選擇createRow方法,這條語句的VALUES子句中的變量是Home接口中create方法的對(duì)應(yīng)參數(shù)。
Database Deployment Settings對(duì)話框(PlayerEJB)
在Entity Deployment Settings對(duì)話框中點(diǎn)擊Database Settings按鈕打開該對(duì)話框。在這里你可以為數(shù)據(jù)庫(kù)指定JNDI名,這是非常重要的,因?yàn)闆]有JNDI名你將無法訪問數(shù)據(jù)庫(kù)。本例中的數(shù)據(jù)庫(kù)JNDI名為jdbc/Cloudscape,用戶名和密碼為空。
四、RosterApp中的方法調(diào)用
為了說明組件之間如何相互協(xié)作,本節(jié)描述實(shí)現(xiàn)特定功能時(shí)的方法調(diào)用順序。這些組件的源文件在j2eetutorial/examples/src/ejb/cmproster目錄下。
“新建”一個(gè)運(yùn)動(dòng)員
1.RosterClient客戶端程序
RosterClient客戶端程序調(diào)用RosterEJB會(huì)話Bean的createPlayer商業(yè)方法。在下面的代碼中myRoster對(duì)象是Roster類型(RosterEJB的遠(yuǎn)程接口),參數(shù)是PlayerDetails對(duì)象,該對(duì)象封裝了一個(gè)運(yùn)動(dòng)員的所有信息。(PlayerDetails是一個(gè)值對(duì)象,用來在遠(yuǎn)程調(diào)用間傳遞實(shí)體信息,關(guān)于值對(duì)象模式的更多信息,參考《J2EE核心模式》一書)。
myRoster.createPlayer(new PlayerDetails("P1", "Phil Jones", "goalkeeper", 100.00));
2.RosetEJB
會(huì)話Bean的createPlayer方法創(chuàng)建一個(gè)PlayerEJB實(shí)體Bean實(shí)例。因?yàn)镻layerEJB只有本地接口,所以create方法在Local Home接口LocalPlayerHome中定義。下面的代碼中playerHome是LocalPlayerHome類型的對(duì)象:
public void createPlayer(PlayerDetails details) {
try {
LocalPlayer player = playerHome.create(details.getId(),
details.getName(), details.getPosition(),
details.getSalary());
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
3.PlayerEJB
ejbCreate方法用set訪問方法將參數(shù)值賦給持久性字段,該方法調(diào)用后容器生成一個(gè)INSERT SQL語句將持久性字段寫入數(shù)據(jù)庫(kù):
public String ejbCreate (String id, String name,
String position, double salary) throws CreateException {
setPlayerId(id);
setName(name);
setPosition(position);
setSalary(salary);
return null;
}
將運(yùn)動(dòng)員加入組
1.RosterClient客戶端
客戶端調(diào)用RosterEJB的addPlayer方法(參數(shù)P1和T1分別代表Player和TeamEJB實(shí)例的主鍵):
myRoster.addPlayer("P1", "T1");
2.RosterEJB
addPlayer方法分兩個(gè)步驟完成工作:首先它調(diào)用findByPrimaryKey查找PlayerEJB和TeamEJB實(shí)例;然后直接調(diào)用TeamEJB的addPlayer方法。代碼如下:
public void addPlayer(String playerId, String teamId) {
try {
LocalTeam team = teamHome.findByPrimaryKey(teamId);
LocalPlayer player =
playerHome.findByPrimaryKey(playerId);
team.addPlayer(player);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
3.TeamEJB
TeamEJB有一個(gè)關(guān)系字段players,它是屬于這個(gè)隊(duì)伍的所有運(yùn)動(dòng)員的集合(Collection),它的訪問方法如下:
public abstract Collection getPlayers();
public abstract void setPlayers(Collection players);
addPlayer方法縣調(diào)用getPlayers方法得到關(guān)聯(lián)的LocalPlayer對(duì)象的集合,然后調(diào)用集合的add方法新加入一個(gè)運(yùn)動(dòng)員:
public void addPlayer(LocalPlayer player) {
try {
Collection players = getPlayers();
players.add(player);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
“刪除”一個(gè)運(yùn)動(dòng)員
1.RosterClient
刪除運(yùn)動(dòng)員P4,客戶端調(diào)用RosterEJH的removePlayer方法:
myRoster.removePlayer("P4");
2.RosterEJB
removePlayer方法調(diào)用findByPrimaryKey方法定位倒要?jiǎng)h除的PlayerEJB實(shí)例然后調(diào)用實(shí)例的remove方法。這個(gè)調(diào)用會(huì)通知容器在數(shù)據(jù)庫(kù)中刪除對(duì)應(yīng)的紀(jì)錄,容器還同時(shí)刪除相關(guān)聯(lián)的TeamEJB的player關(guān)系字段中該實(shí)例的引用以更新TeamEJB-PlayerEJB之間的關(guān)系。下面是RosterEJB的removePlayer方法:
public void removePlayer(String playerId) {
try {
LocalPlayer player =
playerHome.findByPrimaryKey(playerId);
player.remove();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
從組中開除運(yùn)動(dòng)員
1.RosterClient
將運(yùn)動(dòng)員P2從組T1中刪除的客戶端調(diào)用:
myRoster.dropPlayer("P2", "T1");
2.RosterEJB
dropPlayer方法的調(diào)用和addPlayer很像,她先找到PlayerEJB和TeamEJB的實(shí)例,然后調(diào)用TeamEJB的dropPlayer方法:
public void dropPlayer(String playerId, String teamId) {
try {
LocalPlayer player =
playerHome.findByPrimaryKey(playerId);
LocalTeam team = teamHome.findByPrimaryKey(teamId);
team.dropPlayer(player);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
3.TeamEJB
dropPlayer方法會(huì)更新TeamEJB-PlayerEJB關(guān)系。首先它得到關(guān)系字段players 對(duì)應(yīng)的LocalPlayer對(duì)象集合,然后調(diào)用集合的remove方法刪除目標(biāo)運(yùn)動(dòng)員。代碼如下:
public void dropPlayer(LocalPlayer player) {
try {
Collection players = getPlayers();
players.remove(player);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
獲得一個(gè)組里的所有運(yùn)動(dòng)員
1.RosterClient
客戶段調(diào)用RosterEJB的getPlayersOfTeam方法來獲得某個(gè)組的成員,該方法返回包含PlayerDetails對(duì)象的ArrayList。PlayerDetails是PlayerEJB的值對(duì)象(《J2EE核心模式》),包含的數(shù)據(jù)成員playerId、name、salary都是PlayerEJB的持久性字段??蛻舳苏{(diào)用代碼如下:
playerList = myRoster.getPlayersOfTeam("T2");
2.RosterEJB
RosterEJB的getPlayersOfTeam方法先調(diào)用TeamEJB的findByPrimaryKey方法找到對(duì)應(yīng)的實(shí)例,然后調(diào)用TeamEJB的getPlayers方法,最后調(diào)用copyPlayerToDetails方法將每一個(gè)運(yùn)動(dòng)員的數(shù)據(jù)拷貝到PlayDetails中組成返回集合。代碼如下:
public ArrayList getPlayersOfTeam(String teamId) {
Collection players = null;
try {
LocalTeam team = teamHome.findByPrimaryKey(teamId);
players = team.getPlayers();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
return copyPlayersToDetails(players);
}
copyPlayerToDetails方法的實(shí)現(xiàn)如下:
private ArrayList copyPlayersToDetails(Collection players) {
ArrayList detailsList = new ArrayList();
Iterator i = players.iterator();
while (i.hasNext()) {
LocalPlayer player = (LocalPlayer) i.next();
PlayerDetails details =
new PlayerDetails(player.getPlayerId(),
player.getName(), player.getPosition(),
player.getSalary());
detailsList.add(details);
}
return detailsList;
}
3.TeamEJB
TeamEJB的getPlayers方法是關(guān)系字段players的訪問方法:
public abstract Collection getPlayers();
對(duì)本地客戶可用,因?yàn)樗窃贚ocal接口中被定義:
public Collection getPlayers();
該方法向本地客戶返回關(guān)系字段的引用,如果客戶接著修改了得到的結(jié)果,實(shí)體Bean中的關(guān)系字段也跟著被修改(因?yàn)槭且?,所以操作的是同一個(gè)對(duì)象)。例如本地客戶可以用如下方法開除一個(gè)運(yùn)動(dòng)員:
LocalTeam team = teamHome.findByPrimaryKey(teamId);
Collection players = team.getPlayers();
players.remove(player);
下面講到的方法描述如何避免這種危險(xiǎn)。
獲取組成員的副本
這一小節(jié)討論下面兩項(xiàng)技術(shù):
☆ 過濾返回給遠(yuǎn)程客戶端的信息
☆ 防止本地客戶直接修改關(guān)系字段
1.RosterClient
如果你想在返回給客戶端的結(jié)果中過濾掉運(yùn)動(dòng)員的薪水信息,你應(yīng)該調(diào)用RosterEJB的getPlayersOfTeamCopy方法,該方法跟getPlayersOfTeam的唯一區(qū)別就是它把每個(gè)運(yùn)動(dòng)員的薪水都設(shè)置為0。客戶端調(diào)用代碼:
playerList = myRoster.getPlayersOfTeamCopy("T5");
2.RosterEJB
getPlayersOfTeamCopy方法并不像getPlayersOfTeam方法一樣調(diào)用getPlayers訪問方法,它調(diào)用LocalTeam接口中定義的getCopyOfPlayers商業(yè)方法。從getPlayersOfTeamCopy方法得到的返回值不能修改TeamEJB的關(guān)系字段players。代碼如下:
public ArrayList getPlayersOfTeamCopy(String teamId) {
ArrayList playersList = null;
try {
LocalTeam team = teamHome.findByPrimaryKey(teamId);
playersList = team.getCopyOfPlayers();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
return playersList;
}
3.TeamEJB
TeamEJB的getCopyOfPlayers方法返回包含PlayeDetails對(duì)象的ArrayList。為了創(chuàng)建這個(gè)ArrayList,它必須遍歷關(guān)系字段players集合并將每一個(gè)元素的信息拷貝到一個(gè)PlayerDetails對(duì)象中,因?yàn)橐^濾薪水字段,所以只拷貝了salary除外的字段,而salary被直接置為0。當(dāng)客戶端調(diào)用getPlayerOfTeamCopy方法時(shí),隱藏了運(yùn)動(dòng)員的薪水信息。代碼如下:
public ArrayList getCopyOfPlayers() {
ArrayList playerList = new ArrayList();
Collection players = getPlayers();
Iterator i = players.iterator();
while (i.hasNext()) {
LocalPlayer player = (LocalPlayer) i.next();
PlayerDetails details =
new PlayerDetails(player.getPlayerId(),
player.getName(), player.getPosition(), 0.00);
playerList.add(details);
}
return playerList;
}
根據(jù)位置查詢運(yùn)動(dòng)員
1.RosterClient
客戶端調(diào)用RosterEJB的getPlayersByPosition方法:
playerList = myRoster.getPlayersByPosition("defender");
2.RosterEJB
RosterEJB的getPlayersByPosition方法調(diào)用PlayerEJB的findByPosition方法得到特定位置得運(yùn)動(dòng)員集合:
public ArrayList getPlayersByPosition(String position) {
Collection players = null;
try {
players = playerHome.findByPosition(position);
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
return copyPlayersToDetails(players);
}
3.PlayerEJB
LocalPlayerHome接口定義了findByPosition方法:
public Collection findByPosition(String position)
throws FinderException;
因?yàn)镻layerEJB是CMP實(shí)現(xiàn)的實(shí)體Bean,所以實(shí)體Bean類PlayerBean并不實(shí)現(xiàn)查找方法,而是在部署描述符中為每一個(gè)查找方法指定EJB QL查詢。下面是findByPosition方法的EJB QL查詢:
SELECT DISTINCT OBJECT(p) FROM Player p
WHERE p.position = ?1
Deploytool工具會(huì)將上面的EJB QL查詢轉(zhuǎn)換成對(duì)應(yīng)的SELECT語句。在容器調(diào)用findByPositiong方法的時(shí)候,SELECT語句也將被執(zhí)行。
查詢運(yùn)動(dòng)員的運(yùn)動(dòng)項(xiàng)目
1.RosterClient
客戶段調(diào)用RosterEJB的getSportsOfPlayer方法:
sportList = myRoster.getSportsOfPlayer("P28");
2.RosterEJB
RosterEJB的getSportsOfPlayer方法返回一個(gè)運(yùn)動(dòng)員可以參與的運(yùn)動(dòng)項(xiàng)目的String類型的ArrayList,它直接調(diào)用PlayerEJB的getSports方法得到這個(gè)ArrayList。代碼如下:
public ArrayList getSportsOfPlayer(String playerId) {
ArrayList sportsList = new ArrayList();
Collection sports = null;
try {
LocalPlayer player =
playerHome.findByPrimaryKey(playerId);
sports = player.getSports();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
I(yíng)terator i = sports.iterator();
while (i.hasNext()) {
String sport = (String) i.next();
sportsList.add(sport);
}
return sportsList;
}
3.PlayerEJB
PlayerEJB的getSports方法只是簡(jiǎn)單的調(diào)用ejbSelectSports方法。因?yàn)閑jbSelectSports方法的參數(shù)是LocalPlayer類型,所以要傳遞一個(gè)實(shí)體Bean實(shí)例的引用給方法。代碼如下:
public Collection getSports() throws FinderException {
LocalPlayer player =
(team.LocalPlayer)context.getEJBLocalObject();
return ejbSelectSports(player);
}
ejbSelectSports方法的代碼:
public abstract Collection ejbSelectSports(LocalPlayer player)
throws FinderException;
ejbSelectSports的EJB QL查詢語句:
SELECT DISTINCT t.league.sport
FROM Player p, IN (p.teams) AS t
WHERE p = ?1
在部署PlayerEJB前,你要用deploytool來產(chǎn)生對(duì)應(yīng)的SELECT語句。當(dāng)容器調(diào)用ejbSelectSports方法時(shí),也同時(shí)執(zhí)行SELECT語句。
五、運(yùn)行RosterApp應(yīng)用程序
啟動(dòng)用到的軟件
1. 在命令模式下執(zhí)行cloudscape -start命令啟動(dòng)Cloudscape數(shù)據(jù)庫(kù)服務(wù)器。
2. 執(zhí)行j2ee -verbose命令啟動(dòng)J2EE服務(wù)器。
3. 執(zhí)行deploytool命令運(yùn)行deploytool部署工具。
部署該應(yīng)用程序
1. 在deploytool中打開RosterApp.ear(j2eetutorial/examples/ears directory)文件
2. 部署
a) 選定樹視圖中的RosterApp節(jié)點(diǎn)
b) 執(zhí)行Tools\Deploy菜單命令
c) 在Introduction對(duì)話框中,選中Return Client JAR復(fù)選框
d) 在Client JAR File域中輸入文件名(或者用Browse按鈕設(shè)置):j2eetutorial/examples/ears/RosterAppClient.jar
e) 一路Next直到Finish
運(yùn)行客戶端
1. 在命令模式下進(jìn)入j2eetutorial/examples/ears目錄
2. 設(shè)置環(huán)境變量APPCPATH為RosterAppClient.jar所在目錄
3. 執(zhí)行如下命令:
runclient -client RosterApp.ear -name RosterClient -textauth
4. 用戶名:guest。密碼:guest123。
六、用deploytool工具部署CMP實(shí)現(xiàn)的實(shí)體Bean
在第2章講述了打包和部署企業(yè)Bean的基本步驟,這一節(jié)將介紹deploytool部署CMP實(shí)現(xiàn)的實(shí)體Bean時(shí)的不同之處,圖例參考RosterApp配置說明一節(jié)。
指定企業(yè)Bean類型
在新建企業(yè)Bean向?qū)В∟ew\Enterprise Bean菜單)中指定企業(yè)Bean類型和持久性管理機(jī)制。
1.在EJB JAR對(duì)話框中點(diǎn)擊Edit按鈕打開Edite Contents對(duì)話框,添加實(shí)體Bean和關(guān)聯(lián)的實(shí)體Bean需要的類
2.在General對(duì)話框中,選擇Bean類型為:Entity
3.在同一個(gè)對(duì)話框中指定Bean類和用到的接口類(遠(yuǎn)程或者本地或者兩者都有)
4.在Entity Settings對(duì)話框中選擇持久性機(jī)制Container managed persistence(2.0)
選擇持久性字段和抽象模式名
可以在上面提到的Entity Settings對(duì)話框中設(shè)置。這里我們?cè)贓ntity選項(xiàng)頁中設(shè)置(在樹視圖中選中上面新建的實(shí)體Bean節(jié)點(diǎn))。見圖6-5
1.在Fields To Be Persisted類表中,選中需要存儲(chǔ)到數(shù)據(jù)庫(kù)中的字段。這些字段名是根據(jù)命名規(guī)范從定義的訪問方法中讀出的。
2.指定主鍵類合主鍵字段,主鍵字段唯一標(biāo)志一個(gè)實(shí)體Bean實(shí)例
3.在Abstract Schema Name域中輸入一個(gè)抽象模式名,該名字在EJB QL查詢中被引用
為查找方法和Select方法定義EJB QL查詢
在Finder/Select Mothods對(duì)話框中定義EJB QL查詢,見圖6-6。
1.在Entity頁中點(diǎn)擊Finder/Select Methods按鈕
2.在Method表各種選擇要定義EJB QL查詢的方法,在EJB-QL域中輸入語句
產(chǎn)生SQL、指定表創(chuàng)建機(jī)制(Deploy Settings對(duì)話框),指定數(shù)據(jù)庫(kù)JNDI名、訪問用戶名和密碼(Database Settings對(duì)話框),定義關(guān)系(EJB JAR節(jié)點(diǎn)的Relationship頁,圖6-3,Edit Relationships對(duì)話框,圖6-4)都請(qǐng)參考RosterApp配置說明一節(jié)的對(duì)應(yīng)內(nèi)容。
七、CMP的主鍵
主鍵類并不一定是J2SE或者J2EE的標(biāo)準(zhǔn)類庫(kù)中的類(特別是你的主鍵是組合字段的時(shí)候),這是你就要自己新建主鍵類并把它和實(shí)體Bean打包在一起。
主鍵類
下面的例子中,PurchaseOrderKey類為PurchaseOrderEJB實(shí)現(xiàn)一個(gè)組合主鍵,該主鍵由有兩個(gè)數(shù)據(jù)成員productModel和vendorId對(duì)應(yīng)實(shí)體Bean的兩個(gè)持久性字段:
public class PurchaseOrderKey implements java.io.Serializable {
public String productModel;
public String vendorId;
public PurchaseOrderKey() { };
public String getProductModel() {
return productModel;
}
public String getVendorId() {
return vendorId;
}
public boolean equals(Object other) {
if (other instanceof PurchaseOrderKey) {
return (productModel.equals(
((PurchaseOrderKey)other).productModel) &&
vendorId.equals(
((PurchaseOrderKey)other).vendorId));
}
return false;
}
public int hashCode() {
return productModel.concat(vendorId).hashCode();
}
}
對(duì)于CMP實(shí)體Bean的主鍵類有以下要求:
☆ 該類必須是public公有類
☆ 數(shù)據(jù)成員是實(shí)體Bean的持久性字段的子集
☆ 有一個(gè)public的缺省構(gòu)造函數(shù)(沒有實(shí)現(xiàn)任何構(gòu)造函數(shù)或者自己實(shí)現(xiàn)一個(gè)無參構(gòu)造函數(shù)和其他構(gòu)造函數(shù))
☆ 重載hashCode和equals(Object other)方法(繼承自O(shè)bject類)
☆ 可序列化(實(shí)現(xiàn)Serializble接口)
實(shí)體Bean類中的主鍵
在PurchaseBean類中,主鍵類對(duì)應(yīng)字段(vendorId和productModel)的訪問方法定義如下:
public abstract String getVendorId();
public abstract void setVendorId(String id);
public abstract String getProductModel();
public abstract void setProductModel(String name);
下面是PurchaseOrderBean類ejbCreate方法的代碼,它的返回類型是主鍵類但是返回語句缺返回null。雖然不是必需的,null是CMP機(jī)制推薦的返回值。這樣可以節(jié)省資源,因?yàn)閷?shí)體Bean并不需要產(chǎn)生主鍵類的實(shí)例并返回。(This approach saves overhead because the bean does not have to instantiate the primary key class for the return value.這句話有些疑義,它的意思應(yīng)該是說CMP機(jī)制會(huì)自動(dòng)處理這些動(dòng)作,所以我們不必自己多事浪費(fèi)時(shí)間)。
public PurchaseOrderKey ejbCreate (String vendorId, String productModel, String productName)
throws CreateException {
setVendorId(vendorId);
setProductModel(productModel);
setProductName(productName);
return null;
}
產(chǎn)生主鍵值
一些實(shí)體Bean的主鍵值對(duì)于商業(yè)實(shí)體有特殊意義。例如:一個(gè)表示向呼叫中心打入的電話呼叫的實(shí)體Bean,主鍵應(yīng)該包括呼叫被接收時(shí)的時(shí)間戳。也有很多實(shí)體Bean的主鍵值是任意的,只要它們是唯一的。對(duì)CMP實(shí)現(xiàn)的實(shí)體Bean,容器可以自動(dòng)為實(shí)體Bean生成主鍵值,不過它對(duì)實(shí)體Bean有一些額外的要求:
☆ 在部署描述符中,定義主鍵類被為java.lang.Object,不指定主鍵字段
☆ 在Home接口中,findByPrimaryKey方法的參數(shù)是java.lang.Object
☆ 實(shí)體Bean類中,ejbCreate的返回值類型是java.lang.Object
這些實(shí)體Bean的主鍵值保存在只有容器可以訪問的內(nèi)部字段中,你不能讓它和持久性字段或者任何其他數(shù)據(jù)成員產(chǎn)生聯(lián)系,然而你還是可以通過調(diào)用getPrimaryKey方法得到主鍵值,然后你可以調(diào)用findByPrimaryKey來定位實(shí)體Bean實(shí)例了。