|
http://www2.tw.ibm.com/developerWorks/tutorial/content/java/t20031013_ejb_part2.html
這是EJB技術教學系列的第 2 部分,這篇技術教學旨在向您介紹 Enterprise JavaBeans 2.0(EJB)中,容器管理持久性(Container-Managed Persistence, CMP)和容器管理關係(Container-Managed Relationships , CMR)。這些特性是 EJB Entity bean 特有的,與一般短暫存在的Session bean 相比較,Entity bean 通常長時間存在。
透過加入以下這些對Entity bean 的進階支援,Enterprise JavaBeans(EJB)2.0 擴充了早期的1.1 版本 ︰
- 更新 Entity bean 的容器管理持久性(container-managed persistence)
- 對容器管理關係(container-managed relationships) 的支援
- 在配置描述子中定義的用於可移植的
select
和find
方法的 EJB-Query Language(EJB-QL) - 加入 local interface 和 local home interface 來最佳化對同一容器中其它 bean 的存取
如果您想要購買或銷售元件,您很可能希望在您的元件中有一個持久層(persistence),能夠在應用程式伺服器(例如,IBM WebSphere、BEAWebLogic、JBoss/Tomcat 等等)和持久性儲存系統(比如,Oracle、DB2 等等)上跨平台工作。您不必在 EJB 中撰寫低階的 JDBC 呼叫來加入這些功能,這就節省大量時間並降低複雜程度。一旦您掌握 CMP/CMR,使用這種技術來撰寫Entity bean 比在 bean-managed persistence(BMP)的 bean 內部使用低階 JDBC 進行撰寫快。
本技術教學假設您已經閱讀此技術教學系列的第一部分(請參閱參考資料)。再次提醒,如果您對 EJB 還不熟悉,或者您需要重新回憶,我建議您閱讀一本 IBM 技術教學,由 Richard Monson-Haefel 和 Tim Rohaly 合著的 Enterprise JavaBeans Fundamentals。Enterprise JavaBeans Fundamentals 是一篇優秀的技術教學。
應該關心 CMP/CMR 嗎?
容器管理的Entity bean 有什麼用處呢?對於初學者,您不必撰寫低階 JDBC 呼叫來儲存 bean 的狀態,而且您不必撰寫程式碼來管理關係。它都內建在 EJB 框架中。關係的介面直接透過大多數 EJB 工作人員已經很熟悉的 java.util.Collection
和 java.util.Set
建置。非常酷﹗
這個額外功能包括對Entity bean 中持久性欄位的 JavaBeans 元件模式的支援。這樣,就不必公開您的類別變數 ─ 這總讓我感到很奇怪 ─ 而是按照我們都知道並喜歡的 JavaBean 技術標準命名模式來建立 get 和 set 方法。
我不會過於強調這一點。因為 EJB 2.0 容器將支援大多數的 SQL 資料庫(以及其它資料儲存),您可以撰寫使用多種類型資料庫的元件。這使得銷售要求持久儲存的元件更容易。例如,您可以銷售將在使用 Oracle 的 IT 部門或使用 DB2 的商店發揮作用的元件。這樣,您不用使用特定於某個特殊的資料庫的 SQL 撰寫低階 JDBC 呼叫,而是將使用 EJB-QL 來建立尋找程式和選擇方法,並在配置描述子中描述關係。
簡單地說,CMP/CMR 是跨平台元件建立中所缺少的環節。CMP/CMR 將刺激企業級元件市場的成長。此外,CMP/CMR 比低階 JDBC 呼叫更容易使用。CMP/CMR 改正很多不足之處,彌補 CMR 早期版本中缺少的功能。雖然有很多持久性框架,但是沒有一個能和 EJB CMP/CMR 一樣,在那麼多的應用程式伺服器平臺上可用。
對於本技術教學我需要知道什麼?
本技術教學中的範例程式碼可於任何支援 EJB 2.0、遵循 J2EE 的應用伺服器上執行。範例程式碼盡可能是規範的;這樣,所有範例程式碼就可被佈署到 Java 2 SDK,Enterprise Edition 1.3. 一起提供的 J2EE 參考實作中。只要您的應用程式伺服器支援 EJB 2.0(從而支援 CMP/CMR),那麼只修改 Ant 建置腳本和適當的配置描述子,就能將範例程式碼佈署到選擇的應用程式伺服器。
儘管您不必是一位專家,但是本技術教學假設您對 Java 很熟悉,並且對 EJB 有一定程度的瞭解。因為我將講述用 XML 撰寫的配置描述子,所以您應該具有 XML 的基本知識。如果您不熟悉 EJB,我建議您閱讀一篇 developerWorks 技術教學,由 Richard Monson-Haefel 和 Tim Rohaly 所著的 Enterprise JavaBeans Fundamentals(請參閱參考資料)。這是一篇精湛的技術教學。即使您不逐字閱讀該技術教學,我建議您至少將它作為參考。
雖然就本質而言,Ant
(一種類似於 make 的基於 XML、開放來源程式碼的建置系統)方面的知識不是先決條件,但是將有助於您理解範例中出現的建置腳本。
您不需要 JDBC 方面的知識,因為本技術教學中將沒有低階呼叫,但是 SQL 和關係資料庫理論的基本知識是需要的。
本技術教學系列將講述什麼內容?
我沒有撰寫要花費幾天時間來通讀的長篇大論。我將技術教學分為三部分,每部分可以在一小時左右看完。您可以在一個午餐休息時間內完成每篇技術教學的學習。
EJB 2.0 加入大量的特性和功能,本技術教學主要討論 CMP/CMR。這樣,本技術教學假設您具有 EJB 和Entity bean 方面的背景知識。要理解本技術教學,您不必是一位 EJB 專家。技術教學講述了 local interface、配置描述子 CMP、CMR 欄位和關係元素。我們還將全面講解關係類型,如下所示︰
- 一對一
- 一對多
- 多對多
範例中的關係還包括單向支援和雙向支援。這些關係在 XML 配置描述子中定義。
在第 1 部分技術教學中,您透過一個簡單的 EJB 2.0 風格的Entity bean 的範例接觸到 CMP/CMR 和 EJB-QL。這個範例的一部分說明在不需要 Java 實作的情況下,如何使用簡單的 EJB-QL 來建立尋找程式方法。本技術教學正是要讓您適應這些術語和技術,並且增加一個基本範例。
在本技術教學,即該系列的第二篇技術教學中,您將以第一個範例為基礎來學習每種類型的關係以及每種類型的關係方向。您將建立一個客戶端,該客戶端存取您建立的關係來加入、刪除和變更相關成員。
應用程式設計
應用程式設計︰Entity 設計
本技術教學系列中的範例使用一個用於線上內容管理系統的虛構認證系統,它被設計為具有以下這些功能︰
- 將使用者登入到系統
- 認證使用者處於某種角色
- 容許將使用者組織成群組,從而容許進行群組作業
- 儲存如位址和連絡訊息這樣的使用者訊息
- 管理角色、使用者和 群組(管理 = 加入、編輯、刪除)
圖 1︰這篇技術教學中Entity的概述
圖 1 中系統的Entity的概述說明有四個截然不同的Entity︰User、Group、Role 以及 UserInfo。這些Entity中的每一個都有下面這三種關係︰
- 多個 User 與多個 Role 相關聯(多對多)
- 一個
User
有一個 UserInfo(一對一) - 一個 Group 包括多個 User(一對多)
第一篇技術教學說明怎樣將 CMP 建置到 UserManagement Entity bean 中。在這第二篇技術教學中,我將說明怎樣用這些Entity的關係來實作每一個Entity。
範例 3︰一對一的雙向關係
關係
關係具有方向︰單向和雙向。並且,關係有 <multiplicity>
︰一對一、一對多以及多對多。
第一個範例的關係是雙向的,<multiplicity>
為一對一。這個關係將是 User
與儲存在 UserInfo
中的連絡訊息之間的關係。這個範例將完成以下這些工作︰
- 定義
UserInfoBean
Entity bean。 - 將 CMR 欄位新增到
UserBean
中。 - 將一個關係元素新增到 配置描述子中。
- 將程式碼新增到
UserManagement
Session bean 中以使用這個關係。 - 將程式碼新增到客戶端中以使用
UserManagement
Session bean 中的新程式碼。
定義 UserInfoBean
UserInfoBean
和 UserBean
一樣,定義 CMP 欄位。CMP 欄位用來儲存描述 UserBean
的連絡訊息和其它訊息。
這樣,UserInfoBean
在配置描述子中定義一個local interface、一個local home interface、一個Entity bean 實作類別以及一個Entity元素實體。所有這些程式碼都在清單 1 到清單 4 中。
清單 1︰UserInfoBean 的 local interface
/* Local interface */
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
public interface LocalUserInfo extends EJBLocalObject {
public abstract String getEmail();
public abstract LocalUser getUser();
public abstract void setUser(LocalUser user);
public abstract String getDept();
public abstract void setDept(String value);
public abstract String getWorkPhone();
public abstract void setWorkPhone(String value);
public abstract String getExtention();
public abstract void setExtention(String value);
public abstract boolean getEmployee();
public abstract void setEmployee(boolean value);
public abstract String getHomePhone();
public abstract void setHomePhone(String value);
public abstract String getFirstName();
public abstract void setFirstName(String value);
public abstract String getLastName();
public abstract void setLastName(String value);
public abstract String getMiddleName();
public abstract void setMiddleName(String value);
}
清單 2︰UserInfoBean 的local home interface
/* Local home interface */
package com.rickhightower.auth;
import javax.ejb.EJBLocalHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.util.Collection;
public interface LocalUserInfoHome extends EJBLocalHome {
public LocalUserInfo create(
String firstName, String middleName,
String lastName, String email,
String dept, String workPhone,
String extention, String homePhone,
boolean isEmployee)
throws CreateException;
public LocalUserInfo findByPrimaryKey (String email)
throws FinderException;
}
包裹 UserInfoBean
請注意,一個關係中所涉及的Entity必須在同一個配置描述子中定義;這樣,必須在同一個 .jar 文件中包裹這些Entity。UserBean
和 UserInfoBean
在同一個 EJB .jar 文件中,並且在同一個配置描述子中定義。
清單 3︰UserInfoBean 的實作
/* entity bean implementation class */
package com.rickhightower.auth;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.naming.*;
public abstract class UserInfoBean implements EntityBean {
public String ejbCreate(
String firstName, String middleName,
String lastName, String email,
String dept, String workPhone,
String extention, String homePhone,
boolean isEmployee)
throws CreateException {
setEmail(email);
setDept(dept);
setWorkPhone(workPhone);
setExtention(extention);
setHomePhone(homePhone);
setEmployee(isEmployee);
setFirstName(firstName);
setLastName(lastName);
setMiddleName(middleName);
return null;
}
public void ejbPostCreate(
String firstName, String middleName,
String lastName, String email,
String dept, String workPhone,
String extention, String homePhone,
boolean isEmployee)
throws CreateException { }
public abstract String getEmail();
public abstract void setEmail(String value);
public abstract String getFirstName();
public abstract void setFirstName(String value);
public abstract String getLastName();
public abstract void setLastName(String value);
public abstract String getMiddleName();
public abstract void setMiddleName(String value);
public abstract LocalUser getUser();
public abstract void setUser(LocalUser user);
public abstract String getDept();
public abstract void setDept(String value);
public abstract String getExtention();
public abstract void setExtention(String value);
public abstract String getWorkPhone();
public abstract void setWorkPhone(String value);
public abstract boolean getEmployee();
public abstract void setEmployee(boolean value);
public abstract String getHomePhone();
public abstract void setHomePhone(String value);
public void setEntityContext(EntityContext context){ }
public void unsetEntityContext(){ }
public void ejbRemove(){ }
public void ejbLoad(){ }
public void ejbStore(){ }
public void ejbPassivate(){ }
public void ejbActivate(){ }
}
另請注意下面的程式碼︰
public abstract LocalUser getUser();
public abstract void setUser(LocalUser user);
在 UserInfoBean
的 local interface 和實作中都有。這個 setter 和 getter 方法定義這個雙向關係的 CMR 欄位。因為這個關係是雙向的,所以 UserInfo
和 UserInfoBean
都必須有參照對方的 CMR 欄位。
清單 4︰UserInfoBean 的配置描述子
<entity>
<display-name>UserInfoBean</display-name>
<ejb-name>UserInfoBean</ejb-name>
<local-home>com.rickhightower.auth.LocalUserInfoHome</local-home>
<local>com.rickhightower.auth.LocalUserInfo</local>
<ejb-class>com.rickhightower.auth.UserInfoBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>True</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>UserInfoBean</abstract-schema-name>
<cmp-field>
<field-name>firstName</field-name>
</cmp-field>
<cmp-field>
<field-name>middleName</field-name>
</cmp-field>
<cmp-field>
<field-name>lastName</field-name>
</cmp-field>
<cmp-field>
<field-name>email</field-name>
</cmp-field>
<cmp-field>
<field-name>dept</field-name>
</cmp-field>
<cmp-field>
<field-name>workPhone</field-name>
</cmp-field>
<cmp-field>
<field-name>extention</field-name>
</cmp-field>
<cmp-field>
<field-name>homePhone</field-name>
</cmp-field>
<cmp-field>
<field-name>employee</field-name>
</cmp-field>
<cmp-field>
<field-name>email</field-name>
</cmp-field>
<primkey-field>email</primkey-field>
</entity>
將 CMR 欄位新增到 UserBean
您需要將 CMR 欄位新增到 UserBean
的 local interface 和 Entity bean 類別中。
除了 CMR 欄位將分別從 getter 和 setter 方法傳回和傳遞關係中的另一個Entity bean 的 local interface 之外,定義 CMR 欄位和定義 CMP 欄位非常相似。在本例中,getter 方法將傳回 LocalUserInfo
,如下所示︰
public abstract LocalUserInfo getUserInfo();
setter 方法將傳遞 LocalUserInfo
,如下所示︰
public abstract void setUserInfo(LocalUserInfo userInfo);
與 CMP 欄位一樣,實作這些方法的實際程式碼由容器實作來定義。您只需在配置描述子中定義關係,EJB 容器將提供管理關係的程式碼。真棒﹗
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
public interface LocalUser extends EJBLocalObject {
public String getEmail();
public String getPassword();
public LocalUserInfo getUserInfo();
public void setUserInfo(LocalUserInfo userInfo);
}
public abstract class UserBean implements EntityBean {
public abstract LocalUserInfo getUserInfo();
public abstract void setUserInfo(LocalUserInfo userInfo);
}
在配置描述子中定義關係
關係在 <enterprise-beans>
元素之外定義。當您指定關係時,您必須指定關係中所涉及的兩個Entity bean。關係在 <ejb-relation>
元素中定義。關係中的每個角色在 <ejb-relationship-role>
元素中定義。
清單 5︰Entity bean 的關係
<relationships>
<ejb-relation>
<ejb-relation-name>UserHasUserInfo</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>
UserHasUserInfo
</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>userInfo</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>
UserInfoPartOfUser
</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<cascade-delete />
<relationship-role-source>
<ejb-name>UserInfoBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>user</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
<ejb-relationship-role>
有 name、multiplicity、source 以及選擇性的 CMR 欄位,分別由下面這些元素定義︰分別是 <ejb-relationship-role-name>
、<multiplicity>
、帶有 <ejb-name>
子元素的 <relationship-role-source>
以及帶有 <cmr-field-name>
子元素的 <cmr-field>
。
在配置描述子中定義關係
<ejb-relationship-role-name>
元素可以是您所希望的任何名稱。請儘量使它對於您所描述的關係具有描述性。另外,請儘量使它在配置描述子的上下文中是唯一的,如下所示︰
&nbs葛壑云慟ntity /<ejb-name>
定義的 ejb-bean 名稱。
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
/<cmr-field-name>
必須定義 CMR 欄位名稱。如果 <cmr-field>
由 local interface 中的 <getUserInfo()>
和 <setUserInfo()>
定義,那麼它將由 <cmr-field-name>
中的 <userInfo>
定義,如下所示︰
<cmr-field>
<cmr-field-name>userInfo</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
在一個 <ejb-relation>
中總是定義兩個 <ejb-relationship-role>
。如果關係是雙向的,那麼關係的雙方都將有一個 <cmr-field>
。下面是 UserHasUserInfo
關係中的另一方,如下所示︰
<ejb-relationship-role>
<ejb-relationship-role-name>UserInfoPartOfUser</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<cascade-delete />
<relationship-role-source>
<ejb-name>UserInfoBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>user</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
這裡您可以看到這個角色的名稱是 UserInfoPartOfUser
(<ejb-relationship-role-name>
的主體)。您還可以看到Entity bean 是 UserInfoBean
(<relationship-role-source>
/<ejb-name>
的主體)。並且,您可以透過 <cmr-field>
/<cmr-field-name>
來識別出關係中所包括的 CMR 欄位是 user。
將 UserManagement
改為使用關係
為了說明怎樣使用 CMR 欄位,UserManagement
Session bean 將加入三個方法︰
- 使用連絡訊息的新
addUser()
- 傳回一個物件陣列的
getUsersInfo()
方法
- 使用
<userInfo>
CMR 欄位的 changeLastName()
方法
清單 6 和 清單 7 顯示修改過的 UserManagement
bean。
清單 6︰UserManagement 和 UserManagementBean 的介面和實作
/** Remote interface */
public interface UserManagement extends EJBObject {
public void addUser(String email, String password,
String firstName, String middleName,
String lastName, String dept,
String workPhone, String extention,
String homePhone, boolean isEmployee
) throws RemoteException;
public UserValue[] getUsersInfo()throws RemoteException;
public void changeLastName(String email, String lastName)
throws RemoteException;
/** Entity Implementation class */
public class UserManagementBean implements SessionBean {
public void addUser(String email, String password,
String firstName, String middleName,
String lastName, String dept,
String workPhone, String extention,
String homePhone, boolean isEmployee){
try {
LocalUser user = userHome.create(email, password);
LocalUserInfo info = infoHome.create(firstName,
middleName, lastName, email, dept,
workPhone, extention, homePhone,
isEmployee);
user.setUserInfo(info);
} catch (CreateException e) {
throw new EJBException
("Unable to create the local user " + email, e);
}
}
public UserValue[] getUsersInfo(){
try{
ArrayList userList = new ArrayList(50);
Collection collection = userHome.findAll();
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
LocalUser user = (LocalUser)iterator.next();
LocalUserInfo info = user.getUserInfo();
UserValue userValue = new UserValue(user.getEmail());
copyUserInfo(userValue, info);
userList.add(userValue);
}
return (UserValue[])
userList.toArray(new UserValue[userList.size()]);
} catch (FinderException e){
throw new EJBException
("Unable to get list of users ", e);
}
}
public void changeLastName(String email, String lastName){
try {
LocalUser user = userHome.findByPrimaryKey(email);
LocalUserInfo info = user.getUserInfo();
info.setLastName(lastName);
} catch (FinderException e) {
throw new EJBException
("Unable to change the last name " + lastName, e);
}
}
清單 7︰UserManagementBean 的可序列化的 UserValue 類別
/** Serializable value class */
package com.rickhightower.auth;
public class UserValue implements java.io.Serializable{
private String email="";
private String firstName="";
private String middleName="";
private String lastName="";
private String dept="";
private String workPhone="";
private String extention="";
private String homePhone="";
private boolean employee;
public UserValue() {
}
public UserValue(String aEmail) {
email = aEmail;
}
public String getEmail() { return email;}
public void setEmail(String email) {this.email=email;}
public String getFirstName() {return firstName;}
public void setFirstName(String firstName){
this.firstName=firstName;
}
public String getMiddleName() {return middleName;}
public void setMiddleName(String middleName){
this.middleName=middleName;
}
public String getLastName() {return lastName;}
public void setLastName(String lastName) {
this.lastName=lastName;
}
public String getDept() {return dept;}
public void setDept(String dept) {
this.dept=dept;
}
public String getWorkPhone() {return workPhone;}
public void setWorkPhone(String workPhone) {
this.workPhone=workPhone;
}
public String getExtention() {return extention;}
public void setExtention(String extention) {
this.extention=extention;
}
public String getHomePhone() {return homePhone;}
public void setHomePhone(String homePhone) {
this.homePhone=homePhone;
}
public boolean isEmployee() {return employee;}
public void setEmployee(boolean employee) {
this.employee = employee;
}
}
新的 UserManagement
方法
addUser()
addUser()
方法說明如何設定一個 CMR 欄位。
addUser()
方法將所有的連絡訊息和使用者訊息作為參數。
然後,它使用 UserBean
和 UserInfoBean
的 home 介面建立 LocalUser
和 LocalUserInfo
實例,如下所示︰
public void addUser(String email, String password, boolean isEmployee){
LocalUser user = userHome.create(email, password);
LocalUserInfo info = infoHome.create(firstName, middleName, lastName,
email, dept, workPhone, extention, homePhone, isEmployee);
一旦 LocalUser
和 LocalUserInfo
物件都建立好了,addUser()
方法就使用新建立的 UserInfo
設定 User
的 <userInfo>
CMR 欄位,如下所示︰
user.setUserInfo(info);
getUsersInfo()
<getUserInfo()>
方法說明如何取得一個 CMR 欄位。
由於您不能從遠端方法傳回一個本地Entity的集合,<getUserInfo()>
說明怎樣傳回帶有物件的一些Entity bean。
與 getUsers 方法類似,getUsersInfo()
方法使用 UserBean
的 home 介面(LocalUserHome)的 findAll 方法。
但是,不是傳回每個使用者的 UserBean
的識別資料(電子信件),而是傳回每個使用者的一個物件,這個物件包括
每個使用者的部門、電子郵件、就業情況、電話號碼、last name、middle name和 first name,如下所示︰
ArrayList userList = new ArrayList(50);
Collection collection = userHome.findAll();
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
LocalUser user = (LocalUser)iterator.next();
LocalUserInfo info = user.getUserInfo();
UserValue userValue = new UserValue(user.getEmail());
copyUserInfo(userValue, info);
userList.add(userValue);
}
return (UserValue[]) userList.toArray(new UserValue[userList.size()]);
對應每個 User
/UserInfo
對,UserValue
類別被植入資料。
UserValue
類別是一個簡單的可序列化類別,列在左邊。對於每個 User
/UserInfo
對,
getUsersInfo()
方法呼叫名為 copyUserInfo()
的 helper method。
copyUserInfo()
將值從 User
/UserInfo
對複製到UserValue
物件中。下面列出 copyUserInfo()
方法。
private void copyUserInfo(UserValue userValue, LocalUserInfo info){
if (info != null){
userValue.setFirstName(info.getFirstName());
userValue.setMiddleName(info.getMiddleName());
userValue.setLastName(info.getLastName());
userValue.setDept(info.getDept());
userValue.setWorkPhone(info.getWorkPhone());
userValue.setExtention(info.getExtention());
userValue.setHomePhone(info.getHomePhone());
userValue.setEmployee(info.getEmployee());
}
}
changeLastName()
changeLastName()
方法說明存取Entity bean 的 CMR 欄位來修改其中一個相關物件的 CMR 欄位。
changeLastName()
方法使用傳給它的電子郵件來尋找 UserBean
的local interface LocalUser
,
然後使用 LocalUser
的 CMR 欄位來獲得對 UserInfoBean
的存取。
之後,它修改 UserInfoBean
的姓,所有的內容如下所示︰
LocalUser user = userHome.findByPrimaryKey(email);
LocalUserInfo info = user.getUserInfo();
info.setLastName(lastName);
待會將回顧對客戶端的變更來練習這三個新方法。
將客戶端改為使用新的 UserManagement
方法
為了使用 UserManagement
bean 中定義的新方法,正如前面一樣,您必須修改客戶端。
請注意,下面的程式碼使用新的 addUser()
、changeLastName()
和 getUsersInfo()
方法。
事實上,您用 addUsers 建立兩個使用者。然後變更使用者 Andy 的 last name。並且透過利用 UserManagementBean
的 getUsersInfo()
方法列印出 lastName,來驗證last name變更的結果。意外的發現哦﹗
/* Add some user with the new addUser method */
userMgmt.addUser("[email protected]", "starwars",
"Andy", "Mike", "Barfight",
"Engineering", "555-1212", "x102",
"555-5555", true);
userMgmt.addUser("[email protected]", "cusslikeasailor",
"Donna", "Marie", "Smith",
"Marketing", "555-1213", "x103",
"555-7777", true);
System.out.println("Change last name");
userMgmt.changeLastName("[email protected]", "Smith-Barfight");
System.out.println("Get User Info");
UserValue [] users = userMgmt.getUsersInfo();
for (int index=0; index < users.length; index++){
UserValue user = users[index];
System.out.println("user firstName =" + user.getFirstName());
System.out.println("user lastName =" + user.getLastName());
System.out.println("user homePhone =" + user.getHomePhone());
}
編譯和佈署一對一範例
同樣的技術也可以用來建置、包裹、佈署和執行客戶端。請參考本技術教學第一部分的第 8 章,重溫一下怎樣建置和執行這個範例應用程式。請檢視 cmpCmr/section3 下的程式碼和建置文件。
請務必執行 ant clean
來刪除舊範例的文件。
您已經完成最前面的三個範例。如果您對此已經理解透徹,就可以向終點線衝刺了。下一章將講述多對多關係。
範例 4︰多對多的單向關係
多對多關係
多對多關係聽起來極其難以實作。實際上,它和一對一關係一樣容易實作。EJB 容器替您做了所有最難做的工作。
對於這個範例,您將新增使用者可能處於的不同角色。單個使用者可能處於多個角色。並且一個角色可以與多個使用者相關聯。
在這個範例中,您將做下面這些事︰
- 定義
RoleBean
。
- 在配置描述子中定義從
UserBean
到 RoleBean
的關係。
- 將一個 CMR 集合欄位新增到
UserBean
中。
- 將程式碼新增到
UserManagement
Session bean 中以使用這種關係。
- 將程式碼新增到客戶端以使用
UserManagement
Session bean 中的新程式碼。
定義 RoleBean
RoleBean
與上一範例中的 UserInfoBean
類似。和所有其它Entity bean 一樣,它有 CMP 欄位。它還有一個本地 home、一個local interface和一個Entity bean 實作。
與上一個範例中的 UserInfoBean
不同,RoleBean
沒有 <cmr-field>
。下面列出 RoleBean
完整的清單。
清單 8︰RoleBean
local interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
public interface LocalRole extends EJBLocalObject {
public abstract String getName();
public abstract String getDescription();
}
清單 9︰RoleBean
Home interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.util.Collection;
public interface LocalRoleHome extends EJBLocalHome {
public LocalRole create (String name, String description) throws CreateException;
public LocalRole findByPrimaryKey (String email) throws FinderException;
public Collection findAll() throws FinderException;
}
清單 10︰RoleBean
Entity bean 實作類別
package com.rickhightower.auth;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.naming.*;
public abstract class RoleBean implements EntityBean {
public String ejbCreate(String name, String description) throws CreateException {
setName(name);
setDescription(description);
return null;
} public
void ejbPostCreate(String name, String description) throws CreateException{ }
public abstract String getName();
public abstract void setName(String name);
public abstract String getDescription();
public abstract void setDescription(String description);
public void setEntityContext(EntityContext context){ }
public void unsetEntityContext(){ }
public void ejbRemove(){ }
public void ejbLoad(){ }
public void ejbStore(){ }
public void ejbPassivate(){ }
public void ejbActivate(){ }
}
清單 11︰RoleBean
配置描述子
<entity>
<display-name>RoleBean</display-name>
<ejb-name>RoleBean</ejb-name>
<local-home>com.rickhightower.auth.LocalRoleHome</local-home>
<local>com.rickhightower.auth.LocalRole</local>
<ejb-class>com.rickhightower.auth.RoleBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>RoleBean</abstract-schema-name>
<cmp-field>
<description>no description</description>
<field-name>name</field-name>
</cmp-field>
<cmp-field>
<description>no description</description>
<field-name>description</field-name>
</cmp-field>
<primkey-field>name</primkey-field>
<security-identity>
<description></description>
<use-caller-identity></use-caller-identity>
</security-identity>
<query>
<description></description>
<query-method>
<method-name>findAll</method-name>
<method-params />
</query-method>
<ejb-ql>select Object(o) from RoleBean </ejb-ql>
</query>
</entity>
</enterprise-beans>
在配置描述子中定義關係
加入多對多關係的 XML 元素和技術與加入一對一關係的 XML 元素和技術基本相同。唯一的關鍵的不同之處是 multiplicity。RoleBean
的 <ejb-relationship-role>
沒有 CMR 欄位元素,因為從 UserBean
到 RoleBean
的關係是單向的。RoleBean
不知道 UserBean
。下面列出了 <ejb-relation>
元素︰
請注意,UserBeanToRoleBean
的 <multiplicity>
為 Many,而且 UserBeanToRoleBean
的 <cmr-field>
有一個子元素 <cmr-field-type>
。<cmr-field-type>
元素用值為 Many 的 <multiplicity>
的關係定義 Java 程式碼類型。請注意,兩種可能是 java.util.Collection
和 java.util.Set
。因為您選擇 java.util.Collection
以及 CMR 欄位名稱是 roles,那麼您可以猜到會在傳回和設定 java.util.Collection
的 UserBean
的 local interface 中看到一個叫做 roles 的 CMR 欄位(setter 和 getter 方法組)。
將集合 CMR 欄位新增到 UserBean
CMR 欄位傳回並設定很多角色。因此與 UserInfo
的一對一關係的 CMR 欄位不同,您將設定並取得角色的集合,如下所示︰
清單 12︰UserBean
Entity bean 實作類別
public abstract class UserBean implements EntityBean {
public abstract Collection getRoles();
public abstract void setRoles(Collection roles);
}
清單 13︰UserBean
local interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
import java.util.Collection;
public interface LocalUser extends EJBLocalObject {
public Collection getRoles();
public void setRoles(Collection roles);
public String getEmail();
public String getPassword();
public LocalUserInfo getUserInfo();
public void setUserInfo(LocalUserInfo userInfo);
}
就是這樣。它與一對一關係沒有太大區別。接下來,我將講解使用多對多關係的 UserManagement
Session bean 中的方法。
在 UserManagement
bean 中使用多對多關係
UserManagementBean
加入四個方法來作用 Role 以及 User
和角色之間的關係,如下所示︰inRole()
、addRole()createRole()
和 removeRole()
。
createRole()
和 removeRole()
方法只不過是將角色新增到資料儲存以及從資料儲存刪除角色的範本化程式碼 ─ 沒有什麼新內容。
addRole()
addRole()
將一個角色新增到 UserBean
中,如下所示︰
public void addRole(String email, String roleName){
LocalUser user = userHome.findByPrimaryKey(email);
LocalRole role = roleHome.findByPrimaryKey(roleName);
Collection roles = user.getRoles();
roles.add(role);
}
請注意,addRole()
用 roleHome 檢視角色,然後它將角色從 CMR 角色欄位新增到集合中。這樣,當角色被新增到角色集合中時,EJB 容器更新資料儲存。因此,將關係插入到資料儲存的程式碼是 Collection 類別的介面。酷﹗
inRole()
inRole()
方法檢查使用者是否處於某個角色︰
public boolean inRole(String email, String roleName){
LocalUser user = userHome.findByPrimaryKey(email);
Collection roles = user.getRoles();
Iterator iter = roles.iterator();
while (iter.hasNext()){
LocalRole role = (LocalRole)iter.next();
if (role.getName().equals(roleName)) return true;
}
return false;
}
因為資料儲存的 API 與 java.util.Collection
相同,用 CMR 編碼既容易又快速。
使用 UserManagement
的 inRole()
和 addRole()
方法
客戶端使用所有新的 UserManagement
方法,如下所示︰
//Create Roles -- section 4
userMgmt.createRole("admin", "Adminstrator");
userMgmt.createRole("manager", "Content Manager");
userMgmt.createRole("user", "Normal User");
userMgmt.createRole("guest", "Guest User");
//Add roles to user Andy -- section 4
userMgmt.addRole("[email protected]", "admin");
userMgmt.addRole("[email protected]", "user");
//See if Andy is in role admin -- section 4
if (userMgmt.inRole("[email protected]","admin")){
System.out.println("Andy is an admin");
}
//See if Andy is in role manager -- section 4
if (!userMgmt.inRole("[email protected]","manager")){
System.out.println("Andy is not a manager");
}
/* Remove Roles -- section 4 */
userMgmt.removeRole("admin");
userMgmt.removeRole("manager");
userMgmt.removeRole("user");
userMgmt.removeRole("guest");
編譯和佈署多對多範例
同樣的技術也可以用來建置、包裹、佈署和執行客戶端。請參閱本技術教學第一部分的第 8 章,重溫一下怎樣建置和執行這個範例應用程式。請檢視 cmpCmr/section4 下的程式碼和建置文件。
請務必執行 ant clean
來刪除舊範例的文件。
您已經完成最前面的四個範例。如果您對此已經理解透徹,所有的內容都可以迎刃而解,您就可以順風而行。下一章將講述一對多關係。
範例 5︰一對多的雙向關係
一對多關係
一對多關係和其它關係非常相似。和前面一樣,EJB 容器完成最艱巨的工作。
在這個範例中,您將做下面這些事︰
- 定義
GroupBean
。
- 在配置描述子中定義從
UserBean
到 GroupBean
的雙向關係。
- 將一個 CMR 集合欄位新增到
UserBean
中。
- 將程式碼新增到
UserManagement
Session bean 中以使用這個關係。
- 將程式碼新增到客戶端中以使用
UserManagement
Session bean 中的新程式碼。
定義 GroupBean
GroupBean
類似於前兩個範例中的 UserInfoBean
和 RoleBean
。和所有其它的Entity bean 一樣,它有 CMP 欄位。它還有本地 home、local interface和Entity bean 實作。
和第一個關係範例中的 UserInfoBean
一樣,GroupBean
有一個參照 UserBean
的 <cmr-field>
。和 UserInfoBean
不同,GroupBean
中的 <cmr-field>
的使用者是 UserBean
的集合。下面列出 GroupBean
完整的清單︰
清單 14︰GroupBean
local interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
import java.util.Collection;
public interface LocalGroup extends EJBLocalObject {
public abstract Collection getUsers();
public abstract void setUsers(Collection collection);
public abstract String getName();
public abstract String getDescription();
}
清單 15︰GroupBean
Home interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.util.Collection;
public interface LocalGroupHome extends EJBLocalHome {
public LocalGroup create (String name, String description) throws CreateException;
public LocalGroup findByPrimaryKey (String name) throws FinderException;
public Collection findAll() throws FinderException;
}
清單 16︰GroupBean
Entity bean 實作類別
package com.rickhightower.auth;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import java.util.Collection;
import javax.naming.*;
public abstract class GroupBean implements EntityBean {
public String ejbCreate(String name, String description) throws CreateException {
setName(name);
setDescription(description);
return null;
}
public void ejbPostCreate(String name, String description) throws CreateException{ }
public abstract Collection getUsers();
public abstract void setUsers(Collection collection);
public abstract String getName();
public abstract void setName(String name);
public abstract String getDescription();
public abstract void setDescription(String description);
public void setEntityContext(EntityContext context){ }
public void unsetEntityContext(){ }
public void ejbRemove(){ }
public void ejbLoad(){ }
public void ejbStore(){ }
public void ejbPassivate(){ }
public void ejbActivate(){ }
}
清單 17︰GroupBean
配置描述子項目
<entity>
<display-name>GroupBean</display-name>
<ejb-name>GroupBean</ejb-name>
<local-home>com.rickhightower.auth.LocalGroupHome</local-home>
<local>com.rickhightower.auth.LocalGroup</local>
<ejb-class>com.rickhightower.auth.GroupBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>GroupBean</abstract-schema-name>
<cmp-field>
<description>The group name</description>
<field-name>name</field-name>
</cmp-field>
<cmp-field>
<description>The description of the group</description>
<field-name>description</field-name>
</cmp-field>
<primkey-field>name</primkey-field>
<security-identity>
<description></description>
<use-caller-identity></use-caller-identity>
</security-identity>
<query>
<description></description>
<query-method>
<method-name>findAll</method-name>
<method-params />
</query-method>
<ejb-ql>select Object(group) from GroupBean group</ejb-ql>
</query>
</entity>
在配置描述子中定義關係
用來加入一對多關係的 xml 元素和技術與加入一對一和多對多關係的 xml 元素和技術幾乎相同。唯一的關鍵的不同之處是 multiplicity。GroupBean
的 <ejb-relationship-role>
有一個 CMR 欄位元素,因為從 UserBean
到 GroupBean
的關係是雙向的。GroupBean
有一個名為 users 的 CMR 欄位。UserBean
有一個名為 group 的 CMR 欄位。下面列出了 <ejb-relation>
元素︰
<ejb-relation>
<ejb-relation-name>GroupsHaveUsers</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>GroupHasUsers</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>GroupBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>users</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>UsersInGroup</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>group</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
請注意,GroupHasUsers
的 multiplicity 為 One,因為在這個關係中有一個群組,並且和上一個範例 GroupHasUsers
的 <cmr-field>
一樣,有一個子元素 <cmr-field-type>
,如下所示︰
<cmr-field>
<cmr-field-name>users</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
欄位關係的類型是 java.util.Collection
,CMR 欄位名稱是 users。
關係的另一方 UsersInGroup 的 <multiplicity>
為 Many,因為這個群組裡有很多使用者。另請注意將 <cmr-field>
設定為 group。
將 CMR 欄位群組新增到 UserBean
CMR 欄位 group 容許您將這個使用者設定到一個群組中或者取得使用者所在的群組。這樣,和 UserInfo
的一對一關係的 CMR 欄位一樣,您可以設定並取得與群組相關聯的集合︰
清單 18︰UserBean
Entity bean 實作類別
public abstract class UserBean implements EntityBean {
public abstract LocalGroup getGroup();
public abstract void setGroup(LocalGroup group);
}
清單 19︰UserBean
local interface
package com.rickhightower.auth;
import javax.ejb.EJBLocalObject;
import java.util.Collection;
public interface LocalUser extends EJBLocalObject {
public LocalGroup getGroup();
public void setGroup(LocalGroup group);
public Collection getRoles();
public void setRoles(Collection roles);
public String getEmail();
public String getPassword();
public LocalUserInfo getUserInfo();
public void setUserInfo(LocalUserInfo userInfo);
}
它和其它關係範例沒有太大區別。接下來我將講解使用多對多關係的 UserManagement
Session bean 中的方法。
在 UserManagement
中使用一對多關係
UserManagementBean
加入四個方法,每個方法都作業 Groups
以及 User
和 Groups
之間的關係,如下所示︰inGroup()
、moveUserToGroup()
、addRoleToUsers()
、getUsersInGroup()
、createGroup()
、removeGroup()
、getGroups()
和 getRoles()
。
createGroup()
和 removeGroup()
方法都只是將群組新增到資料儲存中以及從資料儲存刪除群組的範本化程式碼 ─ 又是沒有什麼新內容。加入方法 getRoles()
和 getGroups()
來顯示可用的角色和群組 。
inGroup()
inGroup()
方法檢查一個使用者是否在某個群組中︰
public boolean inGroup(String email, String groupName){
LocalUser user = userHome.findByPrimaryKey(email);
return user.getGroup().getName().equals(groupName);
}
moveUserToGroup()
moveUserToGroup()
將使用者移到另一個群組中︰
public void moveUserToGroup(String email, String groupName){
LocalUser user = userHome.findByPrimaryKey(email);
LocalGroup group = groupHome.findByPrimaryKey(groupName);
user.setGroup(group);
/* The relationship works both way so this would work too! */
//<emph>group.getUsers().add(user);
addRoleToUsers()
addRoleToUsers()
使用群組到使用者和角色到使用者的兩種關係。它將 Role 新增到一個群組裡的每個使用者中。
public void addRoleToUsers(String groupName, String roleName){
LocalGroup group = groupHome.findByPrimaryKey(groupName);
LocalRole role = roleHome.findByPrimaryKey(roleName);
Collection users = group.getUsers();
Iterator iterator = users.iterator();
while(iterator.hasNext()){
LocalUser user = (LocalUser)iterator.next();
user.getRoles().add(role);
}
}
getUsersInGroup()
getUsersInGroup()
使用使用者到 <userInfo>
的關係以及使用者到群組的關係,傳回一個字串陣列,每個字串包括群組中每個使用者的名、姓和電子郵件。
public String[] getUsersInGroup(String groupName){
LocalGroup group = groupHome.findByPrimaryKey(groupName);
Collection users = group.getUsers();
Iterator iterator = users.iterator();
ArrayList userList = new ArrayList (50);
while(iterator.hasNext()){
LocalUser user = (LocalUser)iterator.next();
String firstName = user.getUserInfo().getFirstName();
String lastName = user.getUserInfo().getLastName();
String email = user.getEmail();
StringBuffer sUser = new StringBuffer(80);
sUser.append(firstName + ", ");
sUser.append(lastName + ", ");
sUser.append(email);
userList.add(sUser.toString());
}
return (String []) userList.toArray(new String[userList.size()]);
}
現在,我們來修改客戶端,以便可以使用這種程式碼。
使用 UserManagement
的 inGroup()
、moveUserToGroup()
、addRoleToUsers()
和 getUsersInGroup()
方法
客戶端使用所有新的 UserManagement
的方法,如下所示︰
System.out.println("Create and Display groups");
userMgmt.createGroup("marketing", "Marketing group");
userMgmt.createGroup("engineering", "Engineering group");
userMgmt.createGroup("sales", "Sales group");
userMgmt.createGroup("IT","Information Technology group");
/** Display the created groups */
String [] groups = userMgmt.getGroups();
for (int index=0; index < groups.length; index++){
System.out.println(groups[index]);
}
createUsers(userMgmt); //updated for section 3
/** Section 5 Add role to users */
String group = "engineering";
String role = "super_user";
userMgmt.addRoleToUsers(group,role);
group = "IT";
role = "admin";
userMgmt.addRoleToUsers(group,role);
group = "marketing";
role = "user";
userMgmt.addRoleToUsers(group,role);
/** Section 5 -- list users in group engineering */
String [] users = userMgmt.getUsersInGroup("engineering");
System.out.println("Users in the engineering group");
for (int index=0; index < users.length; index++){
System.out.println(users[index]);
}
/** Section 5 -- Is Andy in group Engineering ? */
if (userMgmt.inGroup("[email protected]","engineering")){
System.out.println("Andy is in the engineering group");
}
/** Section 5 --
Is Andy in group the super_user role that was assigned
to all users in engineering? */
if (userMgmt.inRole("[email protected]","super_user")){
System.out.println("Andy is a super_user");
}
/** Section 5 -- Move Andy to marketing */
userMgmt.moveUserToGroup("[email protected]", "marketing");
/** Section 5 -- Is Andy in group Engineering ? */
if (userMgmt.inGroup("[email protected]","engineering")){
System.out.println("Andy is in the engineering group");
} else if(userMgmt.inGroup("[email protected]","marketing")){
System.out.println("Andy is now in the marketing group");
}
/**Section 5 **/
userMgmt.removeGroup("marketing");
userMgmt.removeGroup("engineering");
userMgmt.removeGroup("sales");
userMgmt.removeGroup("IT");
編譯和佈署一對多範例
同樣的技術也可以用來建置、包裹、佈署和執行客戶端。請參閱本技術教學第一部分的第 8 章,重溫一下怎樣建置和執行這個範例應用程式。請檢視 cmpCmr/section5 下的程式碼和建置文件。
請務必執行 ant clean
來刪除舊範例的文件。
結論
現在,您已經完成本技術教學系列最前面的五個範例以及兩篇技術教學的學習。看起來您已經達到預定目標了。
您已經建立一對一、一對多以及多對多關係。您使用集合將Entity新增到其它Entity中,而 EJB 容器將行新增到底層的連線表和欄位中。
總結一下,您可以用 deploytool
檢視 Entity .jar 文件中的關係,如圖 2 和圖 3 所示。
圖 2︰本技術教學中您建立的所有關係
圖 3︰Group 的 CMR 和 CMP 欄位
在本技術教學系列的第三篇,即最後一部分技術教學中,我將說明 EJB-QL 的功能。
參考資料
- 這裡是全部的程式碼 zip 文件
- 一本優秀的關於 EJB CMP/CMR 和 EJB-QL 的技術教學
- Sun 的 J2EE 技術教學
參考書籍
- Ed Roman、Scott W. Ambler、Tyler Jewell、Floyd Marinescu 合著的 Mastering Enterprise JavaBeans (2nd Edition),
EJB 的百科全書﹗
- Richard Monson-Haefel 所著的 Enterprise JavaBeans (3rd Edition),也請買下這本書﹗
- Richard Hightower、Nicholas Lesiecki 合著的 Java Tools for Extreme Programming,講述了用 EJB 建置和佈署 J2EE。
作者
Rick Hightower,eBlox 的開發主管,具有十多年的軟體工作人員經驗。他領導採用新過程(比如,極端編碼)和新技術(比如 CMP 和 CMR)。
Rick 的出版品包括 Java Tools for eXtreme Programming,它講述了佈署和測試 J2EE 工程(由 John Wiley 出版);他參與合著的 Java Developer's Journal(由 Sams 出版),還有 Java Developer's Journal 中發表過幾篇的文章。