EJB 2.0 中的一個示例 CMP 實體 在 EJB 2.0 中,容器管理的實體 bean 被定義為抽象的,而且它的持久性字段並不在 bean 類中直接定義。作為替代,開發了一種抽象的持久性方案,從而允許 bean 提供者間接地聲明持久性字段和 bean 關系。下面是 Employee bean 的一個示例,它使用了新的抽象持久性方案。請注意,該 bean 類中未聲明任何持久性字段。 public abstract EmployeeBean implements Javax.ejb.EntityBean { . // 實例字段 EntityContext ejbContext; // 容器管理的持久性字段 public abstract void setIdentity(int identity); public abstract int getIdentity(); public abstract void setFirstName(String firstName); public abstract String getFirstName(); public abstract void setLastName(String lastName); public abstract String getLastName(); // 容器管理的關系字段 public abstract void setContactInfo(ContactInfo info); public abstract ContactInfo getContactInfo(); ... } 在此 bean 的 XML 部署描述符中,抽象的持久性方案聲明容器管理的各個字段和各種關系。
EmployeeEJB ... Container ... identity firstName lastName ... ContactInfo ContactInfo street city state zip homePhone workPhone email ... Employee-ContactInfo employee-has-contactinfo one EmployeeEJB contactInfo ContactInfo contactinfo_belongsto_employee one ContactInfo 用來描述容器管理的關系的 XML 元素可能變得非常復雜,因為他們必須處理各種關系的對應性和方向(單向的還是雙向的)。上面的代碼段說明,為了描述 bean 與其從屬對象類之間的簡單關系,您需要哪些元素。雖然即使是簡單的關系也會被轉換為冗長的 XML,但所有這些元素都是必需的,以便持久性管理器能夠將復雜的對象圖映射到數據庫中。 雖然用於定義 CMP bean 的抽象持久性方案的 XML 元素是 EJB 2.0 中的 CMP 的主要問題,但為了簡潔起見,本文不再提供 XML 示例。作為替代,本文將純粹依靠 bean 類中必須使用的抽象習語,來說明 EJB 2.0 中的 CMP 背後的基本概念。這些代碼習語與 XML 部署描述符中的關系元素一起使用,並由後者定義,所以您不能只有其一而沒有另一個,但它們比該方案的 XML 部分較容易理解。 除了 XML 元素之外,抽象的持久性方案還定義了一組習語,它們在聲明 bean 類及其相關的對象時必然會用到。用來訪問和修改字段的方法是嚴格定義了的,要求用 set<:METHOD> 方法修改持久性字段,而用 get<:METHOD> 方法訪問它們。這些方法的名稱和返回類型由部署描述符中它們相應的 XML 關系元素規定。 實體 bean 類和從屬類都遵循相同的抽象持久性方案。下面是如何將 ContactInfo 對象定義為從屬對象類的示例。 public abstract class ContactInfo { file:// 家庭地址信息 public abstract void setStreet(String street); public abstract String getStreet(); public abstract void setState(String state); public abstract String getState(); public abstract void setZip(String zip); public abstract String getZip(); public abstract void setHomePhone(String phone); public abstract String getHomePhone(); // 工作地址信息 public abstract void setWorkPhone(String phone); public abstract String getWorkPhone(); public abstract void setEMail(String email); public abstract String getEMail(); ... } 從屬對象隨實體 bean 的存在而存在,隨實體 bean 的中止而中止,這是理解從屬對象與實體 bean 之間關系的關鍵。從屬對象包含在一個具體的實體中,所以刪除這個實體將導致從屬對象也被刪除。用關系數據庫的術語來說,有時這就稱為級聯刪除。 從屬對象,如 ContactInfo,用在關系字段中。與實體 bean 形成關系的從屬對象技術上稱為從屬對象類。EJB 客戶端應用程序永遠不能直接訪問從屬對象類;這種類不能用作 bean 的遠程或本地接口中的參數或返回值。從屬對象類只對 bean 類才是可見的。 從屬對象類不適合作為遠程參數類型,因為它們與 bean 在運行時的持久性邏輯有密切的聯系。持久性管理器擴展了抽象的從屬對象類,以便能提供一種實現,可用於在運行時管理 bean 的持久性狀態。此外,抽象的持久性方案還為數據建模 -- 而不是為那些由企業級 bean 表示的業務概念建模 -- 所以,作為一種設計策略,將抽象的持久性方案對 EJB 客戶機隱藏起來是有意義的。 例如,ContactInfo 關系字段中除了 bean 的客戶機所需的簡單地址信息之外,還包含許多其它信息。雖然您可以使用抽象持久性方案中的從屬對象類 ContactInfo(它對 bean 的客戶機是隱藏的),但是,您得用其它的對象來把這些數據實際表露給客戶機。下面是一個示例,說明了如何對 EJB 客戶機隱藏 ContactInfo 從屬對象。在此例中,地址信息是通過在 EJB 1.1 的示例中開發的 Address 對象來表露的。 // Employee bean 的遠程接口 public interface Employee extends javax.ejb.EJBObject { public Address getHomeAddress(); public void setHomeAddress(Address address); public int getIdentity() throws RemoteException; public void setFirstName(String firstName) throws RemoteException; public String getFirstName()throws RemoteException; public void setLastName(String lastName) throws RemoteException; public String getLastName() throws RemoteException; } // Employee bean 的 bean 類 public abstract EmployeeBean implements javax.ejb.EntityBean { ... public Address getHomeAddress(){ ContactInfo info = getContactInfo(); Address addr = new Address(); addr.street = info.getStreet(); addr.city = info.getCity(); addr.state = info.getState(); addr.zip = info.getZip(); return addr; } public void setHomeAddress(Address addr){ ContactInfo info = getContactInfo(); info.setStreet(addr.street); info.getCity(addr.city); info.getState(addr.state); info.getZip(addr.zip); } .... // 容器管理的關系字段 public abstract void setContactInfo(ContactInfo info); public abstract ContactInfo getContactInfo(); ... } 盡管容器管理的關系字段沒有表露給客戶機,但您仍然可以從遠程接口直接使用容器管理的持久性字段。請注意,用來訪問 firstName 和 lastName 的容器管理的持久性字段是在遠程接口中使用的。 一個 bean 與各種從屬對象類之間可能具有多種不同的關系,它們由這種關系的對應性和方向來定義。Bean 與從屬對象類之間可以有一對多和一對一的關系。例如,Employee bean 可能僅有一個 Benefit 從屬對象類,但可能有許多 ContactInfo 從屬對象類。 public abstract EmployeeBean implements javax.ejb.EntityBean { ... public abstract void setContactInfos(Collection addresses); public abstract Collection getContactInfos(): public abstract void setBenefit(Benefit benefit); public abstract Benefit getBenefit(); ... } 與從屬對象類的一對多關系既可表示為 java.util.Collection 類型,也可表示為 ava.util.Set 類型(注:在本規范的後續版本中,java.util.Map 和 java.util.List 被視為附加的返回類型),而與從屬對象的一對一關系則使用從屬對象的類型。 實體 bean 也可以定義與其它實體 bean 的關系。這些關系可以是一對一、一對多或多對多。例如,Employee bean 可能有許多子級 bean,而只有一個配對的 bean。下面的代碼段使用抽象持久性方案的方法習語,說明了如何為這些關系建模。該應用程序中,子級 bean 和配對的 bean 都表現為 Person bean。 public abstract EmployeeBean implements javax.ejb.EntityBean { ... public abstract void setSpouse(Person manager); public abstract Person getSpouse(); public abstract void setChildren(Collection family); public abstract Collection getChildren(); ... } 與另一個 bean 的一對多關系表示為 java.util.Collection 類型或 java.util.Set 類型,而一對一關系則使用該 bean 的遠程接口類型。 從屬對象本身與同一個 bean 中的其它從屬對象之間可以有一對一、一對多和多對多的關系。此外,從屬對象與其它實體 bean(除其父級 bean 之外)也可以有一對一、一對多的關系。下面的示例顯示,Benefit 從屬對象類與 Salary 從屬對象(一種報酬計算程序)之間怎樣具有一對一的關系,而與 Investment bean 又怎樣具有一對多的關系。 public abstract class Benefit { public abstract void setSalary(Salary salary); public abstract Salary getSalary(); public abstract void setInvestments(Collection investments); public abstract Collection getInvestments(); } 在部署時,部署者將使用持久性管理器工具來具體實現這個 bean 類及其從屬類。這些具體實現將在運行時保持各種關系,並使各 bean 實例的狀態與數據庫同步。容器將在運行時管理持久性實例,從而提供一種強健的環境,其中具有自動的訪問控制和事務控制。 bean 也可以定義從屬對象的值,這些對象是可序列化的對象,如 EJB 1.1 示例中的 Address 對象。這些值通過序列化而變為持久的,它們並不形成與 bean 的關系 -- 它們是嚴格的容器管理的持久性字段。 容器與持久性管理器之間也已經定義了一個合約,使持久性管理器可以獲得事務的句柄,並訪問由該容器管理的數據庫連接池。這個合約稍嫌寬松,將來還需要使其更為嚴格,但它是允許持久性管理器跨 EJB 容器移植的基礎。容器和持久性管理器之間合約的細節已超出了本文的范圍。 除了通過抽象持久性方案定義持久性之外,EJB 2.0 還提供了一種新的查詢語言,用來說明持久性管理器應該如何實現 CMP 中的各種查找方法。 EJB 查詢語言 EJB 查詢語言 (EJB QL) 規定了持久性管理器應該如何實現在本地接口中定義的各種查找方法。 EJB QL 以 SQL-92 為基礎,可由持久性管理器自動編譯,這使得實體 bean 具有更高的可移植性,並且更容易部署。 EJB QL 和查找方法 EJB QL 語句是在實體 bean 的部署描述符中聲明的。使用 EJB QL 非常簡單。作為一個例子,Employee bean 的本地接口可以按以下方式聲明: public interface EmployeeHome extends javax.ejb.EJBHome { ... public Employee findByPrimaryKey(Integer id) throws RemoteException, CreateException; public Collection findByZipCode(String zipcode) throws RemoteException, CreateException; public Collection findByInvestment(String investmentName) throws RemoteException, CreateException; } 給定了上面的本地接口定義之後,您就可以使用 EJB QL 來指定持久性管理器應該如何執行查找方法。每個實體 bean 都必須有一個 findByPrimaryKey() 方法。為執行該方法所需的查詢是很明顯的 -- 使用主關鍵字的(一個或幾個)字段在數據庫中查找 bean,這樣就不需要任何 EJB QL 語句。 findByZipCode() 方法用來獲得具有某個郵政編碼的所有 Employee bean。這將使用部署描述符中的下列 EJB QL 來表達。 FROM contactInfo WHERE contactInfo.zip = ?1 該語句本質上是表示“選擇其郵政編碼等於 zipcode 參數的所有 Employee bean”。 在用於查找方法的 EJB QL 語句中,不需要使用 SELECT 子句來表明要選擇的內容。這是因為,查找方法將總是選擇與其自身的 bean 類型相同的遠程引用。在這種情況下,就可以認為選擇語句將返回遠程 Employee bean 的全部引用。 如果各種查找方法都一起部署在同一個 ejb-jar 文件中,並且其間具有可導航的實際關系,那麼這些查找方法就甚至可以跨越到另一些 bean 的抽象持久性方案中去。例如,findByInvestment() 方法將要求該查找查詢從 Employee 導航到投資 bean 的抽象持久性方案中去。聲明來表達這種查找操作的 EJB QL 語句如下所示。 FROM element IN benefit.investments WHERE element.name = ?1 以上語句是說:“選擇全部這樣的 Employee bean:其獲利從屬對象至少包含一個投資 bean 的引用,並且其名稱等於 findByInvestment() 方法的 investmentName 參數。” EJB QL 和選擇方法 EJB QL 也用於一種稱為 ejbSelect 方法的新查詢方法中,該方法類似於查找方法,只是它僅供 bean 類使用。該方法不在本地接口中聲明,所以也不顯露給客戶機。此外,ejbSelect 方法可返回范圍更大的各種值,而不僅限於 bean 本身的遠程接口類型。 存在兩種選擇方法:ejbSelect<:METHOD> 和 ejbSelect<:METHOD>InEntity。ejbSelect<:METHOD> 方法是全局執行的,這是指這種方法並非專用於執行該方法的 bean 實例。ejbSelect<:METHOD>InEntity 方法則專用於執行該方法的實體實例。這些選擇方法在 bean 類中被聲明為抽象方法,並在這些類的業務方法中使用。下面是 ejbSelect<:METHOD> 方法和 ejbSelect<:METHOD>InEntity 方法的示例,同時說明了可以如何在業務方法中使用它們。 public abstract class EmployeeBean implements javax.ejb.EntityBean { ... file:// ejbSelectInEntity public abstract Collection ejbSelectInvestmentsInEntity (String risk); // ejbSelect public abstract Collection ejbSelectInvestments(String risk); ... } 在上面的聲明中,兩種選擇方法運行於不同的范圍。ejbSelectInvestmentsInEntity() 僅在當前的 Employee bean 實例上執行,所以它只返回雇員的風險投資。 SELECT invest FROM invest IN benefit.investments WHERE invest.type = ?1 另一方面,ejbSelect<:METHOD> 方法的范圍則是全局性的,所以同一個查詢將返回整個企業內所有雇員的全部風險投資。 ejbSelect<:METHOD>InEntity 選擇方法可以返回 bean 的遠程類型(如在上面的查詢中那樣)、從屬對象或任何其它 Java 類型。另一方面,全局選擇方法則不能返回 bean 的從屬對象類型。 選擇方法的 EJB QL 語句要求使用 SELECT 子句,因為它們能夠返回范圍更廣的各種值。 新的 ejbHome 方法 在 EJB 2.0 中,實體 bean 可以聲明一些 ejbHome 方法,用來執行與 EJB 組件相關的操作,但並不專用於某個 bean 實例。在 bean 類中定義的 ejbHome 方法在本地接口中必須有一個與其相匹配的本地方法。下面的代碼說明了一個本地方法,它正是作為 Employee bean 的本地接口定義的。applyCola() 方法用來根據最近 COLA(生活費用調整)的增長來更新所有雇員的薪水。 public interface EmployeeHome extends javax.ejb.EJBHome { file:// 本地方法 public void applyCola(double increate) throws RemoteException; ... } applyCola() 方法在 bean 類中必須有匹配的 ejbHome 方法,它被聲明為 ejbHomeApplyCola()。ejbHomeApplyCola() 方法並非專用於一個 bean 實例,它的范圍是全局的,所以它將對所有雇員的薪水使用同一個 COLA。 public abstract class EmployeeBean implements Javax.ejb.EntityBean { ... // ejbHome 方法 public void ejbHomeApplyCola (double increase ){ Collection col = ejbSelectAllEmployees (); Iterator employees = col.iterator(); while(employees.next()){ Employee.emp = (Employee)employees.next(); double salary =emp.getAnnualSalary(); salary = salary + (salary*increase); emp.setAnnualSalary(salary); } } } bean 的開發人員需要為 BMP 和 CMP 實體 bean 都實現 ejbHome 方法。CMP 實現可能在很大程度上要依賴於全局的選擇語句(如上面所說明的那樣)和 finder 方法,而 ejbHome 的 BMP 實現則將使用直接數據庫訪問和 bean 的 finder 方法,來查詢數據和進行更改。