本文將介紹Java Platform,Enterprise Edition(Java EE 5)的一種設計方法 ,它利用了Enterprise JavaBeans(EJB) 3.0 新的Java Persistence API (JPA) 。JPA 提供了一種標准的對象關系映射解決方案,該解決方案避免了依賴第三方 框架(如 Hibernate)。您將看到示例應用程序的詳細內容,其中驗證了本方法 並闡明關鍵設計決定。
期待已久的下一版本Java EE 5即將發布。Java EE 5許多新功能都包含經過修 補的EJB架構,其突出特性之一是JPA。由於具有容器內和容器外持久性選項,JPA 為 J2EE 架構師帶來一系列全新設計選擇。本文將著重介紹容器內應用程序的設 計,此類應用程序依賴EJB容器提供企業服務,如事務處理和安全性。
我將使用您熟悉的PetStore應用程序進行測試,以證明JPA的功能以及它如何 向傳統J2EE設計模式發起挑戰。本應用程序比較瑣碎,所以不提供詳盡的實現細 節。我將用代碼摘錄對設計注意事項進行說明。本文假設您熟悉EJB 3.0基本概念 和對象關系(OR)映射基本概念。
設計概述
示例PetStore應用程序是基於Web的電子交易應用程序,它實現以下用例:
◆浏覽產品
◆查找產品
◆維護賬戶
◆維護購物車
◆創建訂單
本應用程序被設計為具有三個主要邏輯層的多層Java EE應用程序:
◆表示層(並非本文的重點)使用 Struts 框架。
◆服務層是一種簡單的服務facade,將所有工作委托給其協作者。服務層的目 的是分離服務供應與服務實現。
◆數據訪問層是一系列作為無狀態會話bean實現的粗粒度Data Access Objects (DAO)。出於持久性的需要,它們都依賴 Java 持久性實體管理器。
應用程序域模型由EJB 3.0實體bean表示並用於層間的通信。當域對象離開數 據訪問層時,它與實體管理器脫離。當重新進入數據訪問層時,它需要重新連接 到實體管理器。
注釋似乎是Java 5的一個廣泛采用的特性,JPA也不例外。注釋可用於指定OR 映射——在dW文檔和教程中您經常可以看到——而PetStore應用程序出於相同目 的使用它們。然而值得一提的是您還能通過映射文件的方式指定OR映射。本文稍 後的OR映射 一節將探討並比較這兩種可選方式。
我在Jboss應用服務器中開發並部署PetStore應用程序(參見 參考資料)。我 使用商用數據庫完成大多數開發工作並將應用程序後端移植到 PostgreSQL數據庫 (OR映射 一節包含了關於使用JPA時您應該了解的數據庫遷移的潛在影響的討論 )。
本案例分析的目的之一是符合設計標准,允許高度可測試的實現。如 測試 一 節所見,您能夠使用一系列測試技術來測試PetStore應用程序。
PetStore 應用程序充分利用了這一事實:它是規則的 Web 應用程序。主要優 點是所有層能夠運行在相同的 JVM中,免除了組件分發的需要。本文的 遠程處理 一節簡要介紹了為應用程序添加遠程處理功能的方法。
服務層
服務層被設計為服務facade。它由PetStoreService這一無狀態會話bean實現 。Bean要完全依靠其協作者來提供Web服務。
因為簡化的PetStore要求被限定於從數據庫檢索數據並把數據存儲於數據庫, 惟一的協作者就是DAO。真正的應用程序能夠調用Web服務,通過 RMI/IIOP或資源 適配器訪問其他應用程序,並生成電子郵件消息等。所有此類型的功能都需要其 他協作者支持。
可通過@EJB或@Resource注釋注入協作者(如清單 1 所示)或通過 @PostConstruct方法注入協作者(如清單 2 所示):
清單 1. 使用 @EJB 注入協作者
@EJB(beanName = "AccountDao")
AccountDao accountDao;
清單 2. 使用 @PostConstruct 注入協作者
MessageSource messageSource;
@PostConstruct
public void init() {
messageSource = new MessageSourceImpl("exceptions");
}
選擇bean實現類的測試策略的主要因素是類完全依賴協作者來提供服務。這意 味著類和協作者的交互作用需要被驗證。正如您在 測試 一節看到的,模仿對象 方法完全滿足該目標。
數據訪問層
數據訪問層被設計為一系列粗粒度的DAO。DAO被實現為無狀態會話bean,一個 bean對應一個邏輯域:AccountDao、OrderDao和 ProductDao。
每個 bean 都要把實體管理器注入到其中:
@PersistenceContext(unitName = "manager1")
protected EntityManager em;
這是應用程序中持久性調用類(persistence-aware) 最多的層。它廣泛使用 全新的Enterprise JavaBeans Query Language(EJB QL)。所有持久性相關的行 動都在該層發生,例如:
profile = (UserProfile) em.createQuery(
"from UserProfile up where up.login = :login").setParameter(
"login", login).getSingleResult();
下面是另一個例子:
em.persist(account);
事實上這些類是持久性調用類(persistence-aware),需要一種容器內測試 策略,這將在 測試 一節進行描述。
域模型
您可以把JPA看作是眾所周知的透明持久性技術(如JDO和Hibernate)的繼承 者。盡管透明持久性可看作一個附加(add-on)服務,可被應用到忽略持久性的 Plain Old Java Objects (POJO)中,但JPA還是對域對象施加了少量限制。
首先,您通常要具有一個映射到對應數據庫表主鍵的(代理)對象標識符:
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AccountSeq")
@Column(name = "account_sysid")
public Integer getAccountId() {
return this.accountId;
}
其次,在聯機事務過程(OLTP)環境中,需要一個 version 字段或 JavaBean 屬性以進行樂觀並發控制:
@Column(name = "row_version")
@Version
public int getVersion() {
return version;
}
最終,如果您選擇使用注釋方式的映射,映射注釋將分布在代碼周圍。
另一種限制源自於這樣一個事實:域對象可以通過由實體管理器所管理的域對 象所在的層訪問,也可以通過分離它的所有其他層訪問。此外,對象可能是新的 或已刪除的。一般情況下,對象的行為取決於其持久性狀態。例如,假設在實體 管理器中執行以下這行代碼:
int size = attachedProduct.getItems().size();
項集合使用給定產品的項填充,size變量的值大於0。如果使用默認懶加載 (lazy)配置的一對多關聯,並在表示層中執行同一行代碼,size 變量的值為0 。換句話說,在實體管理器之外(在實體管理器內也沒關系)無法把非活動 (lazy)對象與活動(eager)對象區分開。解決此問題的方法是強制執行項目規 范和約定。
在域對象中,您可以把相同的推理應用到業務方法。無論是否在實體管理器中 ,它們都應該准備好在任何層中調用。這就是把域對象中的業務方法數量限制為 必要的最少數量的理由。
雖然有上述各種情況,域對象依然保留許多POJO特性。這意味著您可以使用 plain old Java test (POJT)對業務方法進行測試,POJT這一術語引自Expert One-on-One J2EE Development without EJB一書。
OR映射
OR 映射是圍繞JPA設計的應用程序的重要元素。它直接影響實體管理器填充域 對象的方式。因此,變更映射能夠在表示層覺察到。變更獲取類型或級聯類型可 能將產生非常不利的影響。
正如前面所述,有兩種定義映射的方法:元數據(注釋)和映射文件。盡管我 們高度提倡采用元數據的方法,但您應該也注意到了它所帶來的不便。從本質上 講,此方法牽涉應用程序的兩個邏輯層:域模型和映射信息。因為這兩個層是分 散的,所以這兩個層需要使用不同技術進行單獨測試。元數據方法本身不會影響 層的可測試性。更確切的說,元數據方法使這兩個層好像只是一個層,由於受一 系列因素的影響,這可能會引起問題。
影響映射方法選擇的一個因素是項目小組結構。在只有少數開發人員參與的小 型項目中,表的數量很少(一般說來少於 100 個表)而且沒有專職的映射人員, 因為通常使用注釋會更加快速,所以把注釋方法看作定義映射也許是最佳選擇。 對於擁有專職映射人員或映射小組的中型和大型項目來講,使用映射文件的方法 才是更好的選擇。該方法可以降低資源爭用並使開發過程具有另一種自由度。基 於元數據的映射方法證實是 PetStore應用程序更加節省時間的方法。
OR映射層的目的是使其余應用程序免受底層數據庫變更的影響。當把PetStore 應用程序後端遷移到PostgreSQL時,無需對映射層作出任何變更。這可能要歸結 於這一事實:原始數據庫和PostgreSQL這兩者均支持序列,因此主鍵生成策略保 持完好。一般情況下,您應該對與對象ID處理相關的映射區域進行重寫。
對映射的全面測試覆蓋極為重要。必須覆蓋所有關系映射以確保對獲取行為和 過渡持久性進行測試。您能夠利用容器外使用的JPA來執行該任務(將在下一節進 行詳細介紹)。
測試
服務層設計的關鍵要素是,關注與底層的協作以提供請求的服務。這需要考慮 到使用動態模仿對象的可靠測試策略。我使用EasyMock框架來實現測試方法。
DAO層具有強大的數據庫內聚力。這就是可靠測試需要某類容器內策略和數據 庫訪問的原因。盡管這對於遠程EJB很容易,還是需要考慮適合本地bean的有意義 的方法。此處令人困擾的因素是:
◆需要容器內測試 facade
◆域對象是非連續的,所以全部驗證需要發生在容器內。
JBoss Embeddable EJB3容器(撰寫這篇文章時尚處於測試版階段)被證實是 更適當的選擇。因為能夠從單元測試啟動JBoss Embeddable EJB3容器,這樣所有 代碼都運行在同一JVM中。使用可嵌入容器的容器內測試可實現它的目標,但過程 比較緩慢,因為容器啟動時間就需要大概30秒。這種問題可能是由較早的產品狀 態造成的,可通過合理的配置改進。
我采用 POJT 對域模型類的業務方法進行測試。不需要其他測試技術,況且其 他測試技術不適合這些類。
OR映射是一個需要窮舉測試覆蓋的主要層。該層對數據庫非常敏感,所以該層 不能應用模仿對象或POJT技術。但是,您可以利用JPA的容器外功能。我就使用這 種策略來測試PetStore或OR映射層。
您需要牢記測試中的測試關聯和過渡持久性行為的重要性。這樣您才能及早注 意到獲取類型的變更或級聯類型值的變更,並采取適當的措施。
遠程處理
PetStore應用程序設計的關鍵特性是它的本地特性。使應用程序的所有邏輯層 運行在同一JVM中,這種方法具有很多優點。有關該主題的詳細討論,請參閱 Expert One-on-One J2EE Development without EJB。
不過,應當說明的是:通過遠程處理 facade,您可以輕松向應用程序添加遠 程處理功能。遠程處理facade(而不是我在前面描述的服務facade)公開了一個 遠程界面,它具有兩個職責:進行域模型和順序數據傳輸對象(DTO)之間的相互 轉換和在服務facade上調用適當的方法。
使用遠程無狀態會話bean能夠實現本應用程序。唯一障礙是創建其他DTO層和 進行它們與域模型之間相互轉換。然而您需要它來確保實現整潔的界面以及與遠 程客戶機的松散耦合。
結束語
EJB 3.0和JPA毫無疑問將是Java EE 5的主要賣點。在某些領域中,它們給正 常的Java社區帶來競爭優勢,並使Java在其他領域與競爭對手不分伯仲。(不可 否認的是,目前某些領域尚不存在基於標准的方法。)
過去數年來,Spring Framework一直是EJB在企業領域的主要競爭對手。EJB 3.0規范解決了很多促進Spring興起的問題。隨著它的出現,EJB 3.0 毫無疑問比 Spring提供了更好的開發體驗--最引人注目的優勢是它不需要配置文件。
JPA提供一種標准的OR映射解決方案,該解決方案完全集成到EJB 3.0兼容的容 器中。JPA的前輩將會繼續穩定發展,但是業務應用程序中的raw 使用將可能會減 少。實現JPA兼容的實體管理器似乎很可能是此類技術的發展方向。
在撰寫本文時,EJB 3.0規范還處在建議的最終草案(Proposed Final Draft )階段。以下是一些未解決的問題以及與JPA相關的預實現:
◆當前形式的JPA規范沒有定義只讀實體bean。這讓人困惑,因為兼容EJB 2.1 規范的實體bean支持這種特性。Spring框架也支持只讀事務。
◆可插入的持久性提供者概念仍處於未交付的階段。
◆標准樂觀並發異常--OptimisticLockException--首次出現在EJB 3.0 Proposed Final Draft中。在持久性提供者執行它以前,您還需要使用特定於提 供者的異常,如Hibernate的StaleObjectStateException,來檢測樂觀並發問題 。暫時,這種
◆情況限制您的實現只能采用特定的持久性提供者。
Java EE系列規范的較大問題與JPA沒有任何關系。Java EE系列規范的問題涉 及到Web和EJB容器之間的集成。Spring在此領域仍然具有主要競爭優勢。JBoss的 Seam項目嘗試使用自定義的方法來解決這一問題。Caucho Resin應用服務器試圖 擴展容器邊界並支持在 Web 容器中使用@EJB注釋。我們希望Java EE 5.1將解決 層集成的問題,為我們提供一個全面而標准的依賴性注入方法。