[讀書筆記]實體Bean

主要內容:

  1. 實體bean如何描述向所有客戶端提供服務的域對象;
  2. 兩種類型的實體bean——bean管理的持久性(BMP)和容器管理的持久性(CMP);
  3. 除遠程接口外,EJB如何提供本地接口;
  4. 指定、實現、配置和部署BMP實體bean;
  5. 從命令行而不是使用圖形用戶界面配置和部署EJB。

注:

  • 域對象:關係型數據庫管理系統中的表。
  • 業務邏輯:指的是規則、約束、過程和實踐的集合;

會話bean和實體bean都是對象,因此業務邏輯既可以處於會話bean中也可以處於實體bean中。

規則和約束通常應用於所有應用程序中。最好通過實體bean實現這種業務邏輯,因爲實體bean是可以在多個不同應用程序中再次使用的域對象。

在業務領域中,過程和實踐通常都是某類應用程序的表達,因此會話bean是實現這類業務邏輯的最佳選擇。實際上,由於計算機可提供新的方法來實現這些任務,因此計算機化系統的引入常常會改變這些過程和實踐(這些改變有時有利、有時有弊)。

實體bean與會話bean之間主要不同之處在於:

  • 實體bean實現的是javax.ejb.EntityBean而不是javax.ejb.SessionBean,因此兩者的生命週期不同;
  • 實體bean使用EntityContext而不是SessionContext進行初始化。EntityContext向實體bean展示一個主鍵,而對於會話bean而言這是不可應用的。

javax.ejb接口的其他細節和會話bean是一樣的。簡要地說:

  • 實體bean的Home和遠程接口分別通過擴展EJBHome和EJBObject來定義;
  • 本地Home和本地接口則分別通過擴展EJBLocalHome和EJBLocalObject進行定義;
  • EJBMetaData類提供對實體bean組件各部分的訪問;
  • Handle和HomeHandle接口可序列化到遠程bean或Home的引用。

實體bean的類型:

實體bean描述的是純處在關係型數據庫管理系統中的共享持久型數據或其他持久型數據存儲集。如果數據存儲集是關係型的,實際執行JDBC的職責可由bean本身或EJB容器承擔。

前一種情況稱爲bean管理的持久性(BMP),後一種情況稱爲容器管理的持久性(CMP)。

注意:EJB規範主要面向的是關係型數據存儲集。當然,可通過JDBC的javax.sql.DataSource對象執行容器管理的持久性,JDBC是基於ANSI SQL92的。

遠程接口和本地接口

EJB2.0規範較之早期版本最爲重要的一個改進就是本地接口和遠程接口的調用。

能夠調用bean的方法而無需考慮bean的位置對於會話bean而遠是非常關鍵的,但是對於實體bean來說用處不大,甚至有不利的影響。客戶端要完成一些工作常常必須處理多個實體bean,如果每一個實體bean都是通過遠程的,就會導致網絡擁塞。同時,爲執行“通過值進行傳遞”語法,需要克隆人和序列化對象,這也會影響執行效率。

更令人沮喪的是,實體bean的客戶端很可能是一個會話bean。實際上,使用非會話bean與實體bean進行交互通常被看作一種不好的習慣。時常,此會話bean作爲正在使用的實體bean被協同定位。僅因爲實體bean是遠程的,EJB容器要通過網絡負責完成全部會話——實體bean的調用,並克隆所有的序列化對象。

到目前爲止,你可能以猜測到什麼是本地接口,他們是實體bean可指定的非遠程接口。同樣這裏使用Home和代理的概念,即本地接口從javax.ejb.EJBLocalHome擴展而來,而bean代理從javax.ejb.EJBLocalObject擴展而來。這些接口均爲常規java接口,應用接口間傳遞的對象的“通過引用進行傳遞”語法。

一個實體bean可提供常規的Home/遠程接口,或提供本地Home/本地接口。實際上,無法阻止實體bean提供這兩種接口,儘管任何客戶端使用遠程接口都會導致前文提到的運行性能下降。本地接口並非實體bean專有,會話bean也可以提供本地接口。對於會話bean而言(尤其是無狀態的會話bean),我們有理由爲其提供遠程和本地兩種接口。通常我們希望這兩種接口提供相同的性能,儘管EJB規範對此並未作強制性的要求。

本地接口不僅僅是EJB性能推動器,同時還是容器管理的持久性和容器管理的關係(CMR)建立的基礎。

BMP實體Bean的生命週期

BMP和CMP實體bean的生命週期由bean必須實現的EntityBean接口規定。

然而,儘管方法名字是相同的,但這些方法的BMP和CMP實體bean承擔的責任卻不相同。

生命週期如下:

  • 如果EJB容器需要一個實體bean實例(例如,如果實例遲太小),它會示例bean實例,並調用其setEntityContext()方法;
  • 彙總的實例可爲查找方法在描述現有bean的持久性數據存儲集中定位數據;
  • bean可採用下面兩種方法的任何一種與EJBLocalObject代理(如果使用遠程接口,則爲EJBObject代理)進行關聯。
  1. 第一種方法,通過ejbActivate()有容器激活bean。bean的代理此時存在,但並未與bean關聯。如果先前曾鈍化bean並在代理上調用過業務方法,則出現這種情況。如果bean的代理作爲查找方法的結果被返回,也會出現這種情況。
  2. 第二種方法,客戶端可能正在請求通過ejbCreate()和ejbPostCreate()創建一個實體bean。通常這意味着相應的數據已經插入到了持久性數據存儲集。
  • 當bean已經與代理進行關聯後,可在其上調用業務方法。在代理向bean委派業務方法之前,調用ejbLoad()生命週期方法,表明bean應該重新從事久性數據存儲集中裝載其狀態。完成業務方法後,馬上調用ejbStore()方法,表明bean應該使用自身狀態的改變來更新持久性數據存儲集。
  • bean可採用下述兩種方法中的任何一種返回彙集的狀態:
  1. 第一種方法,通過ejbPassivate()鈍化bean。在生命週期中。通常不需要做什麼事情,這是因爲在先前的ejbStore()方法中bean的狀態已經被保存之持久性數據存儲集。因此,鈍化僅僅表示EJBLocalObject代理和bean之間的連接已經被切斷。
  2. 第二種方法,客戶端可能正在請求通過ejbRemove()刪除已創建的bean。這通常意味着持久性數據存儲集中的相應數據已經被刪除。
  • 最後,如果EJB容器需要,可通過首先調用unsetEntityContext()減少實例池的大小。

本地Home接口

注意bean的ejbCreate()和ejbFindXxx()方法的返回類型與本地Home接口種方法的返回類型是不同的。尤其是,當bean返回主鍵對象或主鍵對象集合時,本地Home接口方法返回(至客戶端)本地代理或代理集合。

  1. 創建和刪除方法
  • 本地方法拋出的異常列表和bean的對應方法應該是匹配的。對於createXXX()方法而言,列表應該是ejbCreateXXX()和ejbPostCreateXXX()所拋出的異常的聯合。如果爲實體bean提供Home和遠程接口,則必須爲Home接口的方法聲明java.rmi.RemoteException異常。
  • 除了create()方法之外,本地Home接口還從javax.ejb.EJBLocalHome繼承了Remove(Object o)方法,此方法對應於bean本身的ejbRemove()生命週期方法。

2. 查找方法

  • bean中的查找方法返回單一的主鍵(如單一的bean匹配底層查詢)或主鍵的java.util.Collection(如果有多個匹配bean)。
  • 通常要求ejbFindByPrimaryKey()方法作爲bean方法中的一個,儘管此方法並不是EntityBean接口的組成部分。這是因爲參數的類型和返回的類型取決於bean。在Collections API引入J2SE1.2以前,該方法來自EJB1.0,還沒有被使用。

3.定製主鍵類

當兩個或多個域識別bean時,需要一個客戶開發的主鍵類;否則使用bean來描述間的單一字段類型。

定製主鍵類需要遵循很多規則:

  • 類必須實現java.io.Serializable或java.io.Externalizable;
  • 類的值必須全部爲基本的或是到序列化對象的引用;
  • 必須實現equals()和hashCode()方法;
  • 必須有一個無參構造器(也可以有其它帶有參數的構造器,但提供這些構造器只是爲了方便起見)。

換句話說,類必須被認爲是值類型的。

例:一個主鍵類

1:package data;

2:

3:import java.io.*;

4:import javax.ejb.*;

5:

6:public class JobPK implements Serializable{

7: public String ref;

8: public String customer;

9:

10: public JobPK(){

11: }

12: public JobPK(String ref, String customer){

13: this.ref=ref;

14: this.customer=customer;

15: }

16:

17: public String getRef(){

18: return ref;

19: }

20: public String getCustomer(){

21: return customer;

22: }

23:

24: public boolean equals(Object obj){

25: if(obj instanceof JobPK){

26: JobPK pk=(JobPK)obj;

27: return(pk.ref.equals(ref) && pk.customer.equals(customer));

28: }

29: return false;

30: }

31: public int hashCode(){

32: return (ref.hashCode()^customer.hashCode());

33: }

34: public String toString(){

35: return "JobPK: ref" " + ref + "", customer=" "+customer + "" ";

36: }

37: }

注意ref和customer字段有公共的可視性,這是EJB規範的一項要求。每個字段——名字和類型——對應於bean自身的一個字段。這個要求看起來可能有點奇怪,但EJB容器需要他來管理CMP bean。

要實現equals()方法,檢驗對象中的所有字段是否與提供對象中的字段有着相同的值。對於原始值而言,應該使用常規操作符==,但對於對象引用而言,則必須調用equals()方法。

要實現hashCode()方法,需要生成一個完全基於這些字段的int值,如下所示:

if A.equals(B) then A.hashCode()==B.hashCode();

有許多方法可以實現這一要求。比較簡潔快速的方法就是將主鍵類字段中全部的值轉換成String,將它們連接成一個單一的String,然後對這個合成的字符串調用hashCode()。同樣,所有字段的hashCode()值都可使用操作符連接在一起。在運行時,這種方式的執行速度要比串聯方式快,但這卻是意味着分發哈希碼對於帶有多個字段的主鍵不是件什麼好事。

4.Home方法

處查找、創建和刪除方法之外,還可以在本地Home接口種定義Home方法。Home方法可用來執行與一個bean及相關的某些業務類型功能。換句話說,Home方法是Java類方法的EJB等價物(使用static關鍵字進行定義)。

Home方法的一些常見用法包括:定義所有bean實例能夠執行的批處理操作(例如降低所有商品的價格)或各種不同的方法,例如爲toString()方法格式化bean的狀態。

本地Home接口程序清單:JobLocalHome.java

package data;

import java.rmi.*;
import java.util.*;
import javax.ejb.*;

public interface JobLocalHome extends EJBLocalHome
{
JobLocal create (String ref, String customer) throws CreateException;
JobLocal findByPrimaryKey(JobPK key) throws FinderException;
Collection findByCustomer(String customer) throws FinderException;
Collection findByLocation(String location) throws FinderException;
void deleteByCustomer(String customer);
}

本地接口

和會話bean與遠程接口之間的關係類似,本地接口定義實體bean的能力。由於實體bean最主要的是描述數據,因此在通過本地接口展現的方法中,毫無疑問多數應該是簡單的獲取和設置方法。

本地接口程序清單:JobLocal .java

package data;

import java.rmi.*;
import javax.ejb.*;
import java.util.*;

public interface JobLocal extends EJBLocalObject
{
String getRef();
String getCustomer();
CustomerLocal getCustomerObj(); // derived

void setDescription(String description);
String getDescription();

void setLocation(LocationLocal location);
LocationLocal getLocation();

Collection getSkills();
void setSkills(Collection skills);
}

注意:setLocation()方法接受的是LocationLocal引用,而不是包含位置名字的String。也就是說,Job Bean 定義本身與其它bean之間的關係,在此情況下,Location Bean 直接,有效地執行引用的完整性。這樣需要Job實體bean的客戶段提供有效位置。

這並不是說實體bean不能提供進一步的處理。常常引用的一個例子是用於SavingAccountBean的,它可提供withdraw()和deposit()方法。withdraw()方法確保餘額永遠不小於零。這個bean也可以提供applyInterest()方法,但幾乎肯定不會提供setBanlance()方法

這些方法在bean中都有與之對應的方法,並且異常清單也嚴格匹配。這些方法的實現在後面“實現本地接口方法”一節進行講述。

BMP實體bean的實現

實現一個實體bean包括:

  • 爲javax.ejb.EntityBean中的方法提供實現;
  • 爲本地Home接口中的每個方法對應方法;
  • 併爲本地接口中的每個方法對應一個方法。

實現javax.ejb.EntityBean方法

setEntityContext()方法是執行JNDI查詢的理想場所,例如獲得一個JDBC數據源引用。

package data;

import javax.ejb.*;

import javax.naming.*;

import javax.sql.*;

public class JobBean implements EntityBean

{

public void setEntityContext(EntityContext ctx) {
this.ctx = ctx;
InitialContext ic = null;
try {
ic = new InitialContext();
dataSource = (DataSource)ic.lookup("java:comp/env/jdbc/Agency");
skillHome = (SkillLocalHome)ic.lookup("java:comp/env/ejb/SkillLocal");
locationHome = (LocationLocalHome)ic.lookup("java:comp/env/ejb/LocationLocal");
customerHome = (CustomerLocalHome)ic.lookup("java:comp/env/ejb/CustomerLocal");
}
catch (NamingException ex) {
error("Error looking up depended EJB or resource",ex);
return;
}
}

private Context ctx;

private DataSource dataSource;

//code omitted

}

unsetEntityContext()方法通常將這些字段設爲null。

package data;

import javax.ejb.*;

import javax.naming.*;

import javax.sql.*;

public class JobBean implements EntityBean

{

private DataSource dataSource;
private SkillLocalHome skillHome;
private LocationLocalHome locationHome;
private CustomerLocalHome customerHome;

private String ref;
private String customer;
private String description;
private LocationLocal location;

private CustomerLocal customerObj; // derived

// vector attribute; list of SkillLocal ref's.
private List skills;

public void unsetEntityContext() {
this.ctx = null;
dataSource = null;
skillHome = null;
locationHome = null;
customerHome = null;
}

//code omitted

}

ejbLoad()ejbStore()方法確保bean狀態和持久性數據存儲集同步。


public void ejbLoad(){
JobPK key= (JobPK)ctx.getPrimaryKey();
Connection con = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
"SELECT description,location FROM Job WHERE ref = ? AND customer = ?");

stmt.setString(1, key.getRef());
stmt.setString(2, key.getCustomer());
rs = stmt.executeQuery();

if (!rs.next()) {
error("No data found in ejbLoad for "+key,null);
}
this.ref = key.getRef();
this.customer = key.getCustomer();
this.customerObj = customerHome.findByPrimaryKey(this.customer); // derived
this.description = rs.getString(1);
String locationName = rs.getString(2);
this.location = (locationName != null)?locationHome.findByPrimaryKey(locationName):null;

// load skills
stmt = con.prepareStatement(
"SELECT job, customer, skill FROM JobSkill WHERE job = ? AND customer = ? ORDER BY skill");

stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
rs = stmt.executeQuery();

List skillNameList = new ArrayList();
while (rs.next()) {
skillNameList.add(rs.getString(3));
}

this.skills = skillHome.lookup(skillNameList);
}
catch (SQLException e) {
error("Error in ejbLoad for "+key,e);
}
catch (FinderException e) {
error("Error in ejbLoad (invalid customer or location) for "+key,e);
}
finally {
closeConnection(con, stmt, rs);
}
}

public void ejbStore(){
Connection con = null;
PreparedStatement stmt = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
"UPDATE Job SET description = ?, location = ? WHERE ref = ? AND customer = ?");

stmt.setString(1, description);
if (location != null) {
stmt.setString(2, location.getName());
} else {
stmt.setNull(2, java.sql.Types.VARCHAR);
}
stmt.setString(3, ref);
stmt.setString(4, customerObj.getLogin());
stmt.executeUpdate();

// delete all skills
stmt = con.prepareStatement(
"DELETE FROM JobSkill WHERE job = ? and customer = ?");

stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.executeUpdate();

// add back in all skills
for(Iterator iter = getSkills().iterator(); iter.hasNext(); ) {
SkillLocal skill = (SkillLocal)iter.next();

stmt = con.prepareStatement(
"INSERT INTO JobSkill (job,customer,skill) VALUES (?,?,?)");

stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.setString(3, skill.getName());
stmt.executeUpdate();
}
}
catch (SQLException e) {
error("Error in ejbStore for "+ref+","+customer,e);
}
finally {
closeConnection(con, stmt, null);
}
}

在ejbLoad()方法中,JobBean使用JobSkill表中的數據作爲skills字段的值,且必須從Job和JobSkill數據表中裝載其狀態。在ejbStore()方法中,對Job和JobSkill表會進行同樣的修改。

當然,有時當bean保存自身時,他的數據已經被刪除了。如果用戶手工刪除數據就有可能出現這種情況;EJB規範沒有要求實體bean“鎖定”底層數據。在此情況下,bean應該拋出javax.ejb.NoSuchEntityException異常;然後,此異常作爲某種類型的java.rmi.RemoteException異常被返回值客戶端。

更復雜的bean在ejbLoad()和ejbStore()方法中可執行其它的處理。例如,在關係型數據庫中,由於性能方面的原因數據可能以反向規範化的形式進行存儲。客戶端無需瞭解這些持久性方面的問題。

另一種想法;可使用這些方法有效地處理文本。EJB規範建議壓縮和解壓縮文本,但也對文本中的關鍵字進行搜索,然後分開存儲這些關鍵字,或將數據轉化爲XML格式。


當鈍化或激活實體bean時,通常不需要做什麼事情。

public void ejbPassivate(){
ref = null;
customer = null;
customerObj = null;
description = null;
location = null;
}

public void ejbActivate(){
}

實現本地Home接口方法

JobBean的ejbCreate()和ejbPostCreate()方法的實現


public JobPK ejbCreate (String ref, String customer) throws CreateException {

// validate customer login is valid.
try {
customerObj = customerHome.findByPrimaryKey(customer);
} catch(FinderException ex) {
error("Invalid customer.", ex);
}

JobPK key = new JobPK(ref,customer);
try {
// workaround; should call ejbHome.findByPrimaryKey, but
// ctx.getEJBHome() returns null...
/*
JobLocalHome jobHome = (JobLocalHome)ctx.getEJBHome();
System.out.println("jobHome = " + jobHome + ", key = " + key);
*/
ejbFindByPrimaryKey(key);
throw new CreateException("Duplicate job name: "+key);
}
catch (FinderException ex) {}

Connection con = null;
PreparedStatement stmt = null;
try {
con = dataSource.getConnection();
stmt = con.prepareStatement(
"INSERT INTO Job (ref,customer) VALUES (?,?)");

stmt.setString(1, ref);
stmt.setString(2, customerObj.getLogin());
stmt.executeUpdate();
}
catch (SQLException e) {
error("Error creating job "+key,e);
}
finally {
closeConnection(con, stmt, null);
}
this.ref = ref;
this.customer = customer;
this.description = description;
this.location = null;

}

 

public void ejbPostCreate (String ref, String customer) {}

此實現可有效地驗證客戶的存在與否(通過客戶和唯一引用來識別職位),同時還可證實完整主鍵已經不存在於數據庫中了。如果確實不存在,那麼BMP bean拋出CreateException異常。否則(由拋出FinderException異常的ejbFindByPrimaryKey()調用所描述),方法繼續。

另一種引用在關係型數據庫管理系統中的Job表內設置唯一索引,然後捕獲當試圖插入復鍵時可能拋出的SQLException異常。

警告:這兒有一個紊亂的情況。用戶有可能在復鍵檢查和實際的SQL語句INSERT之間插入一條記錄。在事物中調用ejbCreate()方法;關係型數據庫管理系統隔離級別的更改(採用EJB容器制定的形式)可杜絕這種風險,儘管此時可能出現死鎖現象。

注意skills字段被設置爲一個空的ArrayList。它存儲SkillLocal引用列表,此列表是到Skill bean的一個本地接口。當然,對新建的Job bean而言,這個列表是空的。skills字段之所以存儲到SkillLocal對象的引用,而不是存儲包含技能名稱的String,這是經過深思熟慮的。如果技能名稱被使用,那麼查找技能的相關信息需要額外的步驟。這也是爲CMP bean和容器管理的關係採用的方法,CMP中將詳細講述這方面的知識。

另外要注意的是customerObj字段。當Job被創建時,只被傳遞一個含有客戶名字的String。此String是客戶主鍵。customerObj字段藉助CustomerLocal引用包含一個到父客戶bean的引用。

skills和customerObj字段演示bean管理的關係。對於skills字段而言,這是一個多對多的關係(從Job到Skill)。對於customerObj字段而言,這是多對一的關係(從Job到customer)。

ejbCreate()和ejbPostCreate()方法都對應bean的本地Home接口中一個名爲create()的單一方法。參數列表必須對應。同樣,對於會話bean而言,也可有多個帶有不同參數的創建方法,或使用createXXX()方法命名規則而不是超載create()的方法名字。

ejbRemove()方法與ejbCreate()方法是相對的;ejbRemove()方法從持久性數據存儲集中刪除一個bean的數據。JobBean中ejbRemove()方法的實現如下:


public void ejbRemove(){
JobPK key = (JobPK)ctx.getPrimaryKey();

Connection con = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
con = dataSource.getConnection();

stmt1 = con.prepareStatement(
"DELETE FROM JobSkill WHERE job = ? and customer = ?");

stmt1.setString(1, ref);
stmt1.setString(2, customerObj.getLogin());

stmt2 = con.prepareStatement(
"DELETE FROM Job WHERE ref = ? and customer = ?");

stmt2.setString(1, ref);
stmt2.setString(2, customerObj.getLogin());

stmt1.executeUpdate();
stmt2.executeUpdate();
}
catch (SQLException e) {
error("Error removing job "+key,e);
}
finally {
closeConnection(con, stmt1, null);
closeConnection(con, stmt2, null);
}
ref = null;
customer = null;
customerObj = null;
description = null;
location = null;
}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章