關于EJB的持久化對象技術分析
表格型的關系型數據庫與樹型Java對象之間的映射問題是一個至今爭論不休的問題,好在現在已經有了一些好的解決方案。在本文中,我們將介紹EJB技術是怎樣用自已特定的方式來解決這個問題的。
只要是涉及到保存及查詢信息 ,那絕大多數應用程序都需要與關系數據庫打交道。但由于關系數據庫與Java對象在結構上有著本質的區別,關于它們之間的映射關系對于那些Java開發者們來說,是一個很令人頭痛的問題。關系型數據庫是以表格方式存儲數據的,而Java對象是以樹型方式表現的。這種類型上的不匹配引發了各種各樣的對象持久化解決方案,用來縮小關系世界與對象世界之間的鴻溝。EJB框架正是這種解決方案之一。
對象的持久性
目前有很多不同的工具出現,使得開發人員可以將JAVA對象轉化為數據庫中的字段或記錄,或者將數據庫中的字段或記錄還原為JAVA對象。這些處理涉及到要將樹型的JAVA對象序列化到數據庫中,反之亦然。此工作的核心是怎樣在保證最小性能損失的前提下,來完成這項工作。
EJB框架提供了這樣一個對象持久化機制。我們將在本文中討論這種機制,但首先我們還是對EJB構架作一個全面的認識。
企業級JavaBeans(EJB)
EJB技術可以這樣定義:它是一個基于JAVA服務端的,為分布式應用提供的一個可復用的組件框架。所有的商業邏輯、部署定義、對象持久性都由這個框架統一管理,EJB框架的一些特色如下:
· EJB是一種運行在服務端環境下的JAVA對象。
· EJB能分布在不同的機器上進行遠程訪問,但對客戶端來而言,調用EJB與調用本地JavaBean一樣方便。
· EJB容器對EJB進行統一管理。
盡管企業級JavaBean的名字與普通JavaBean在命名上有些相像,但它們在設計上有著本質上的區別。為了能讓你更清楚地認識到這點,我們最好先了解一下EJB的基本概念、幾種EJB組件模式和其配置環境。
EJB的運行環境
從本質上來說,EJB只是實現了特定接口的普通JAVA對象,但這個對象必須運行在一個特定的環境:EJB容器中。如果脫離了EJB容器,EJB是無法運行的。EJB與EJB容器之間的關系有時候被稱為"反向調用"――或者叫"好萊塢原理"(別聯系我,到時候我會給你打電話的)。
EJB容器是一種用來管理EJB的運行時環境。它容納并管理不同類型的EJB,這與JAVAservlet容器管理servlet有些類似。EJB容器負責初始化EJB,并給其提供系統級的服務。
當客戶端程序要調用某一個EJB時并不直接與EJB打交道,客戶端與EJB被容器隔離起來。
EJB容器提供的服務
當開發者創建一系列的類與接口,用來組成一個EJB時,容器會為他們提供如下的系統級服務:
· 事務處理
· 安全管理
· EJB的持久化管理
· EJB的遠程訪問
· EJB的生命周期管理
· 數據庫連接池
· EJB的實例池管理
由于EJB容器負責為EJB提供這種底層服務,使得一個EJB開發者只需關注具體應用的商業邏輯,從而減少了很多不必要的麻煩。
EJB的類型
EJB規范定義了以下三種不同類型的EJB類型:
· 消息驅動EJB(MDB)
· 會話EJB
· 實體EJB
當客戶端與會話EJB或實體EJB交互時,它們的通信方式是同步通信。而消息驅動EJB(MDB)則只與JMS進行交互,它相當于JMS中的一個發布/訂閱主題。
消息驅動EJB
消息驅動EJB工作在異步通信模式下。一個消息驅動EJB充當一個消息偵聽者的角色,它偵聽從JMS的發布/訂閱主題中傳來的消息。
EJB容器管理著消息驅動EJB的生命周期,然而與會話EJB和實體EJB不同之處在于客戶端并不能直接調用它的方法。消息驅動EJB是通過一個名為onMessage的回調函數來接收客戶端的消息的。
會話EJB
會話EJB的特點是不能同時被多個客戶端共享。當客戶端調用會話EJB的方法時,先經過EJB容器處理,然后再由容器對會話EJB進行調用。會話EJB處理開發者編寫商業邏輯,容器再將處理結果返回給客戶端。會話EJB不能在多個會話中持久保存。它分為兩種類型:有狀態的會話EJB和無狀態的會話EJB。
有狀態的會話EJB
當一個客戶端與某一個有狀態的會話EJB開啟一個會話時,這個EJB為客戶端維護了一個會話狀態。這暗示著客戶端向此EJB發出不同的調用請求之間保證EJB的成員變量值不會丟失。
一旦客戶端結束與有狀態的會話EJB的交互后,EJB容器會自動銷毀它。于是整個會話結束,并且此有狀態的會話EJB所保存的狀態數據會全部丟失。
無狀態會話EJB
無狀態會話EJB并不為客戶端保存任何狀態數據。你可以這樣認為:客戶端每次對無狀態會話EJB的調用都會產生一個新的EJB實例,因此所有的狀態信息都不會保存。 同樣,EJB容器也不會持久化任何無狀態會話EJB,因此開發者必須意識到客戶端與無狀態會話EJB之間進行交互時,所有的狀態數據都是臨時的。無狀態會話EJB的這種特性使得容器可以重復地使用它的實例,因此無狀態會話EJB能得到比有狀態會話EJB更好的性能。
實體EJB
實體EJB表達的的是一種持久存儲的商業邏輯,通常存儲于關系型數據庫中。實體EJB與關系型數據庫有如下的相似之處:
· 實體EJB是持久的――它可以在應用程序的生命周期之外存在,甚至可以在EJB容器的生命周期以外存在。
· 實體EJB允許共享訪問――多個客戶端可以共享同一個實體EJB,而容器負責管理它們之間的同步。
· 實體EJB有主鍵――主鍵用來確定實體EJB的一個唯一實例,利用它可以找到一個特定的持久化實體。
· 實體EJB有事務的概念――由于客戶端能并發訪問并修改它的數據,因此事務管理是非常重要的。事務管理屬性被顯示地定義在部署描述文件中,而容器負責管理事務的邊界。
要實現對象-關系映射,那實體EJB必須能提供插入、更新、查詢、刪除的操作。而用于管理實體EJB對象與數據源之間的映射的過程被稱為持久化。換句話說,持久化是一個將信息寫入外部數據源的一個過程。EJB規范定義了實體EJB的兩種持久化方式:Bean自身管理的持久化(BMP)和容器管理的持久化(CMP)。
Bean自身管理的持久化(BMP)
如果你選用BMP,那你必須在你的代碼中負責維護所有的持久化發。那么所有的數據層訪問代碼都必須由開發者來完成,這種方式能帶給開發者更大的靈活性。
容器管理的持久化(CMP)
如果你選用CMP,那你不用編寫數據層訪問代碼,EJB容器將會為你管理所有的持久化。因此,數據層訪問代碼與數據源之間是松耦合的。這能減輕開發者的代碼編寫量,并且使得CMP能部署到不同廠商的應用服務器中,也不必關心具體的數據源(參見圖1)。
EJB部署與運行時環境
我們將以JBoss3.0.2作為EJB部署與運行時環境的服務器。我們將設計一個簡單的WEB應用,它允許創建用戶帳號,用戶通過訪問WEB瀏覽器,而WEB瀏覽器通過調用一個servlet來取得這個帳號,這個servlet與一個實體EJB相互通信(參見圖2)。
編寫并部署一個實體EJB
以下四個步驟是開發一個實體EJB的典型流程:
1. 為你的實體EJB編寫相應的類及接口
2. 編寫相應的部署描述文件
3. 將實體EJB及相應的部署描述文件打包成為一個jar文件
4. 部署此實體EJB
一個實體EJB至少由以下三個類(接口)組成:
組件接口――在本例中我們只考慮從同一JVM虛擬機中訪問實體EJB,因此我們需要繼承javax.ejb.EJBLocalObject接口。
Bean類――如果你要開發會話EJB,那么需要實現javax.ejb.SessionBean接口,如果是實體EJB,那么需要實現javax.ejb.EntityBean接口(參見列表1)。
列表1. EJB類
部署描述文件
要將你開發的EJB部署到EJB容器中去的話,那你必須為此容器提供一個部署描述文件。部署描述文件是一個XML格式的文檔,文件名為ejb-jar.xml,里面包含有Bean的持久類型以及事務屬性。你必須將這個文件與編寫的Java類一起打包到一個jar或ear文件中去。
ejb-jar.xml是由SUN公司提供的一個標準部署描述文件,JBoss還有另外一個名為jaws.xml的部署描述文件(參見列表2),這個文件描述了CMP定義以及其持久屬性。
列表2. The ejb-jar.xml Deployment Descriptor
列表2描述了主鍵的名字和類型,以及實體EJB的哪些域將被持久化。如果關系數據庫中沒有一個名為"UserEJB"的表的話,那容器會自動建立一個相應的表。
在列表3中,每一個域都定義了一個相應的cmp-field元素。當容器自動建立一個新表時,它會由此得知要創建那些新的字段,并將域與特定的字段對應起來。
列表3. jaws.xml部署描述文件
UserEJB的部署方法如下:先將部署描述文件與編譯好的類一起打成一個jar包,然后將這個包放在JBoss服務器的deploy目錄下就可以了,JBoss會自動發現這個包,并自動進行部署。
EJB客戶端
在本例中,你可以在同一JVM虛擬機中訪問EJB。這種設計簡化了我們的一些工作,要獲得EJB的home接口,我們需要進行一個特殊的下溯造型。列表4簡述了UserService對象,它用來訪問我們的EJB。
列表4. UserService對象
請注意在列表4中,UserService對象首先試著通過findByPrimaryKey()方法來找到一個EJB的實例,并在這個方法中傳遞一個用戶ID的參數。如果沒有找到實體EJB的實例,UserService對象將調用EJB的home接口中的create()方法來創建一個新的EJB實例。
findByPrimaryKey()方法在調用過程中首先被EJB容器攔截,EJB容器試著從數據庫中找出一個與用戶ID相同主鍵的記錄。create()方法將在數據庫中插入一個主鍵等于用戶ID的記錄。
EJB查詢語言(EJBQL)
EJB規范提供了一種名為EJBQL的對象查詢語言,用來查詢CMP。這種對象查詢語言實際上被容器轉化為SQL語言。
EJBQL語言用來實現EJB的home接口中的find()查詢方法,并執行實體EJB定義的一些內部的select方法。這些EJBQL一般放在應用程序的部署描述文件中。
以下XML代碼(選自于jaws.xml文件)定義了在UserEJB的home接口中findByState()方法與相應EJBQL之間的映射。這個示例程序用這個EJBQL查詢語言及相應的find()方法尋找給出狀態的某個用戶。
在以上的定義當中,ejb-ql標簽定義了實際使用的EJB QL。當你只想查找一個特定類型的對象時,必須要在查詢語言中加入object關鍵字。另外這個查詢中的"?1"的作用類似于JDBC中的PrepareStatement,表明這個查詢是一個參數化查詢。如果有多個參數,則依次為"?2"、"?3"以此類推,這些參數與查詢方法findByState()中的參數表從"?1"開始一一對應。
在上例中,"?1"將被findByState()方法的第一個名為state的參數所取代。除此之外,CMP還有一個名為findAll()或findByPrimaryKey()的
內置方法,你只需要定義它們,而不需實現任何代碼,EJB容器會為你自動生成關于這兩個方法的代碼。
在列表5中,UserService類定義了一個名為getUsersByState()的方法。這個方法調用UserEJB的home接口上的findByState()方法,EJB容器會攔截這個方法,然后執行定義在jaws.xml文件中的EJBQL查詢語言。
列表5. 通過用戶狀態來查找用戶信息
由于Java樹型對象與表格型的關系數據庫中在構架上的差異,對于開發者來說,將Java對象持久化到關系型數據庫中這一工作是一件非常復雜的事情。它們之間的這種差異引發了若干種對象持久技術的產生,以使得關系型世界與對象世界之間的鴻溝日益縮小。EJB框架提供了一個基于容器管理的持久化機制,如果使用得當,將會是一種提供給開發者的優秀解決方案
只要是涉及到保存及查詢信息 ,那絕大多數應用程序都需要與關系數據庫打交道。但由于關系數據庫與Java對象在結構上有著本質的區別,關于它們之間的映射關系對于那些Java開發者們來說,是一個很令人頭痛的問題。關系型數據庫是以表格方式存儲數據的,而Java對象是以樹型方式表現的。這種類型上的不匹配引發了各種各樣的對象持久化解決方案,用來縮小關系世界與對象世界之間的鴻溝。EJB框架正是這種解決方案之一。
對象的持久性
目前有很多不同的工具出現,使得開發人員可以將JAVA對象轉化為數據庫中的字段或記錄,或者將數據庫中的字段或記錄還原為JAVA對象。這些處理涉及到要將樹型的JAVA對象序列化到數據庫中,反之亦然。此工作的核心是怎樣在保證最小性能損失的前提下,來完成這項工作。
EJB框架提供了這樣一個對象持久化機制。我們將在本文中討論這種機制,但首先我們還是對EJB構架作一個全面的認識。
企業級JavaBeans(EJB)
EJB技術可以這樣定義:它是一個基于JAVA服務端的,為分布式應用提供的一個可復用的組件框架。所有的商業邏輯、部署定義、對象持久性都由這個框架統一管理,EJB框架的一些特色如下:
· EJB是一種運行在服務端環境下的JAVA對象。
· EJB能分布在不同的機器上進行遠程訪問,但對客戶端來而言,調用EJB與調用本地JavaBean一樣方便。
· EJB容器對EJB進行統一管理。
盡管企業級JavaBean的名字與普通JavaBean在命名上有些相像,但它們在設計上有著本質上的區別。為了能讓你更清楚地認識到這點,我們最好先了解一下EJB的基本概念、幾種EJB組件模式和其配置環境。
EJB的運行環境
從本質上來說,EJB只是實現了特定接口的普通JAVA對象,但這個對象必須運行在一個特定的環境:EJB容器中。如果脫離了EJB容器,EJB是無法運行的。EJB與EJB容器之間的關系有時候被稱為"反向調用"――或者叫"好萊塢原理"(別聯系我,到時候我會給你打電話的)。
EJB容器是一種用來管理EJB的運行時環境。它容納并管理不同類型的EJB,這與JAVAservlet容器管理servlet有些類似。EJB容器負責初始化EJB,并給其提供系統級的服務。
當客戶端程序要調用某一個EJB時并不直接與EJB打交道,客戶端與EJB被容器隔離起來。
EJB容器提供的服務
當開發者創建一系列的類與接口,用來組成一個EJB時,容器會為他們提供如下的系統級服務:
· 事務處理
· 安全管理
· EJB的持久化管理
· EJB的遠程訪問
· EJB的生命周期管理
· 數據庫連接池
· EJB的實例池管理
由于EJB容器負責為EJB提供這種底層服務,使得一個EJB開發者只需關注具體應用的商業邏輯,從而減少了很多不必要的麻煩。
EJB的類型
EJB規范定義了以下三種不同類型的EJB類型:
· 消息驅動EJB(MDB)
· 會話EJB
· 實體EJB
當客戶端與會話EJB或實體EJB交互時,它們的通信方式是同步通信。而消息驅動EJB(MDB)則只與JMS進行交互,它相當于JMS中的一個發布/訂閱主題。
消息驅動EJB
消息驅動EJB工作在異步通信模式下。一個消息驅動EJB充當一個消息偵聽者的角色,它偵聽從JMS的發布/訂閱主題中傳來的消息。
EJB容器管理著消息驅動EJB的生命周期,然而與會話EJB和實體EJB不同之處在于客戶端并不能直接調用它的方法。消息驅動EJB是通過一個名為onMessage的回調函數來接收客戶端的消息的。
會話EJB
會話EJB的特點是不能同時被多個客戶端共享。當客戶端調用會話EJB的方法時,先經過EJB容器處理,然后再由容器對會話EJB進行調用。會話EJB處理開發者編寫商業邏輯,容器再將處理結果返回給客戶端。會話EJB不能在多個會話中持久保存。它分為兩種類型:有狀態的會話EJB和無狀態的會話EJB。
有狀態的會話EJB
當一個客戶端與某一個有狀態的會話EJB開啟一個會話時,這個EJB為客戶端維護了一個會話狀態。這暗示著客戶端向此EJB發出不同的調用請求之間保證EJB的成員變量值不會丟失。
一旦客戶端結束與有狀態的會話EJB的交互后,EJB容器會自動銷毀它。于是整個會話結束,并且此有狀態的會話EJB所保存的狀態數據會全部丟失。
無狀態會話EJB
無狀態會話EJB并不為客戶端保存任何狀態數據。你可以這樣認為:客戶端每次對無狀態會話EJB的調用都會產生一個新的EJB實例,因此所有的狀態信息都不會保存。 同樣,EJB容器也不會持久化任何無狀態會話EJB,因此開發者必須意識到客戶端與無狀態會話EJB之間進行交互時,所有的狀態數據都是臨時的。無狀態會話EJB的這種特性使得容器可以重復地使用它的實例,因此無狀態會話EJB能得到比有狀態會話EJB更好的性能。
實體EJB
實體EJB表達的的是一種持久存儲的商業邏輯,通常存儲于關系型數據庫中。實體EJB與關系型數據庫有如下的相似之處:
· 實體EJB是持久的――它可以在應用程序的生命周期之外存在,甚至可以在EJB容器的生命周期以外存在。
· 實體EJB允許共享訪問――多個客戶端可以共享同一個實體EJB,而容器負責管理它們之間的同步。
· 實體EJB有主鍵――主鍵用來確定實體EJB的一個唯一實例,利用它可以找到一個特定的持久化實體。
· 實體EJB有事務的概念――由于客戶端能并發訪問并修改它的數據,因此事務管理是非常重要的。事務管理屬性被顯示地定義在部署描述文件中,而容器負責管理事務的邊界。
要實現對象-關系映射,那實體EJB必須能提供插入、更新、查詢、刪除的操作。而用于管理實體EJB對象與數據源之間的映射的過程被稱為持久化。換句話說,持久化是一個將信息寫入外部數據源的一個過程。EJB規范定義了實體EJB的兩種持久化方式:Bean自身管理的持久化(BMP)和容器管理的持久化(CMP)。
Bean自身管理的持久化(BMP)
如果你選用BMP,那你必須在你的代碼中負責維護所有的持久化發。那么所有的數據層訪問代碼都必須由開發者來完成,這種方式能帶給開發者更大的靈活性。
容器管理的持久化(CMP)
如果你選用CMP,那你不用編寫數據層訪問代碼,EJB容器將會為你管理所有的持久化。因此,數據層訪問代碼與數據源之間是松耦合的。這能減輕開發者的代碼編寫量,并且使得CMP能部署到不同廠商的應用服務器中,也不必關心具體的數據源(參見圖1)。
![]() 圖1會話EJB與實體EJB的關系:此圖顯示了EJB容器在客戶端與EJB實例中充當的代理角色。 |
EJB部署與運行時環境
我們將以JBoss3.0.2作為EJB部署與運行時環境的服務器。我們將設計一個簡單的WEB應用,它允許創建用戶帳號,用戶通過訪問WEB瀏覽器,而WEB瀏覽器通過調用一個servlet來取得這個帳號,這個servlet與一個實體EJB相互通信(參見圖2)。
![]() 圖2.通過web訪問EJB:此圖顯示了一個客戶端請求是怎樣從客戶端傳到應用層的。當這個作為控制器的servlet接收到客戶請求后,它將這個請求轉化一個業務請求并向業務服務層調用相應的服務。業務服務層使用一個或多個實體EJB來從數據層中取得或保存數據。 |
編寫并部署一個實體EJB
以下四個步驟是開發一個實體EJB的典型流程:
1. 為你的實體EJB編寫相應的類及接口
2. 編寫相應的部署描述文件
3. 將實體EJB及相應的部署描述文件打包成為一個jar文件
4. 部署此實體EJB
一個實體EJB至少由以下三個類(接口)組成:
組件接口――在本例中我們只考慮從同一JVM虛擬機中訪問實體EJB,因此我們需要繼承javax.ejb.EJBLocalObject接口。
| 2. package com.jeffhanson.datatier.ejb; 3. 4. import javax.ejb.EJBLocalObject; 5. 6. public interface LocalUser extends EJBLocalObject 7. { 8. public String getUserID(); //主鍵 9. public String getFullName(); 10. public String setAddress(String address); 11. public String getAddress(); 12. public String setCity(String city); 13. public String getCity(); 14. public String setState(String state); 15. public String getState(); 16. public String setZip(String zip); 17. public String getZip(); } 18. Home接口――同樣,由于我們處于同一JVM虛擬機中,因此我們需要繼承javax.ejb.EJBLocalHome接口。 19. package com.jeffhanson.datatier.ejb; 20. 21. import javax.ejb.CreateException; 22. import javax.ejb.FinderException; 23. import javax.ejb.EJBLocalHome; 24. import java.util.Collection; 25. public interface LocalUserHome extends EJBLocalHome 26. { 27. public LocalUser create(String userID, 28. String fullName, 29. String address, 30. String city, 31. String state, 32. String zip) 33. throws CreateException; 34. 35. public Collection findByFullName(String fullName) 36. throws FinderException; 37. 38. public LocalUser findByPrimaryKey(String userID) 39. throws FinderException; } |
Bean類――如果你要開發會話EJB,那么需要實現javax.ejb.SessionBean接口,如果是實體EJB,那么需要實現javax.ejb.EntityBean接口(參見列表1)。
列表1. EJB類
| package com.jeffhanson.datatier.ejb; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.CreateException; import java.util.Locale; public class UserEJB implements EntityBean { // 地區缺省設為美國英語 private Locale locale = Locale.US; transient private EntityContext ctx; public String USERID; public String FULLNAME; public String ADDRESS; public String CITY; public String STATE; public String ZIP; public UserEJB() {} public void setLocale(Locale locale) { this.locale = locale; } //訪問CMP域相關的方法 public void setUserID(String userID) { USERID = userID; } public String getUserID() //主鍵 { return USERID; } public void setFullName(String fullName) { FULLNAME = fullName; } public String getFullName() { return FULLNAME; } public void setAddress(String address) { ADDRESS = address; } public String getAddress() { return ADDRESS; } public void setCity(String city) { CITY = city; } public String getCity() { return CITY; } public void setState(String state) { STATE = state; } public String getState() { return STATE; } public void setZip(String zip) { ZIP = zip; } public String getZip() { return ZIP; } public String ejbCreate(String userID, String fullName, String address, String city, String state, String zip) { System.out.println("ejbCreate called with userID: " + userID); setUserID(userID); setFullName(fullName); setAddress(address); setCity(city); setState(state); setZip(zip); return userID; } public void ejbPostCreate(String userID, String fullName, String address, String city, String state, String zip) throws CreateException { // 容器在調用ejbCreate()方法后會自動調用它 System.out.println("ejbPostCreate called with userID: " + userID); } public void setEntityContext(EntityContext ctx) { this.ctx = ctx; System.out.println("setEntityContext called"); } public void unsetEntityContext() { ctx = null; } public void ejbActivate() { // 當此EJB被載入內存之前容器會自動調用它 } public void ejbPassivate() { // 當此EJB被交換入固定存儲器之前 // 容器會自動調用它 } public void ejbLoad() { // 容器調用,用來更新實 // 體EJB的狀態 } public void ejbStore() { // 容器調用,用來將實體 // EJB的狀態存儲入數據庫中 } public void ejbRemove() { // 當實體EJB從數據中刪除之前被 // 容器調用 } } |
部署描述文件
要將你開發的EJB部署到EJB容器中去的話,那你必須為此容器提供一個部署描述文件。部署描述文件是一個XML格式的文檔,文件名為ejb-jar.xml,里面包含有Bean的持久類型以及事務屬性。你必須將這個文件與編寫的Java類一起打包到一個jar或ear文件中去。
ejb-jar.xml是由SUN公司提供的一個標準部署描述文件,JBoss還有另外一個名為jaws.xml的部署描述文件(參見列表2),這個文件描述了CMP定義以及其持久屬性。
列表2. The ejb-jar.xml Deployment Descriptor
| <?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <display-name>Users</display-name> <enterprise-beans> <entity> <description>封閉一個用戶對象</description> <ejb-name>UserEJB</ejb-name> <local-home>com.jeffhanson.datatier.ejb.LocalUserHome</local-home> <local>com.jeffhanson.datatier.ejb.LocalUser</local> <ejb-class>com.jeffhanson.datatier.ejb.UserEJB</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>USERID</field-name></cmp-field> <cmp-field><field-name>FULLNAME</field-name></cmp-field> <cmp-field><field-name>ADDRESS</field-name></cmp-field> <cmp-field><field-name>CITY</field-name></cmp-field> <cmp-field><field-name>STATE</field-name></cmp-field> <cmp-field><field-name>ZIP</field-name></cmp-field> <primkey-field>USERID</primkey-field> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>UserEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> |
列表2描述了主鍵的名字和類型,以及實體EJB的哪些域將被持久化。如果關系數據庫中沒有一個名為"UserEJB"的表的話,那容器會自動建立一個相應的表。
在列表3中,每一個域都定義了一個相應的cmp-field元素。當容器自動建立一個新表時,它會由此得知要創建那些新的字段,并將域與特定的字段對應起來。
列表3. jaws.xml部署描述文件
| <?xml version="1.0" encoding="ISO-8859-1"?> <jaws> <datasource>java:/DefaultDS</datasource> <type-mapping>Hypersonic SQL</type-mapping> <enterprise-beans> <entity> <ejb-name>UserEJB</ejb-name> <create-table>true</create-table> <table-name>UserEJB</table-name> <remove-table>false</remove-table> <tuned-updates>false</tuned-updates> <read-only>false</read-only> <time-out>300</time-out> <cmp-field> <field-name>USERID</field-name> <column-name>USERID</column-name> </cmp-field> <cmp-field> <field-name>FULLNAME</field-name> <column-name>FULLNAME</column-name> </cmp-field> <cmp-field> <field-name>ADDRESS</field-name> <column-name>ADDRESS</column-name> </cmp-field> <cmp-field> <field-name>CITY</field-name> <column-name>CITY</column-name> </cmp-field> <cmp-field> <field-name>STATE</field-name> <column-name>STATE</column-name> </cmp-field> <cmp-field> <field-name>ZIP</field-name> <column-name>ZIP</column-name> </cmp-field> </entity> </enterprise-beans> </jaws> |
UserEJB的部署方法如下:先將部署描述文件與編譯好的類一起打成一個jar包,然后將這個包放在JBoss服務器的deploy目錄下就可以了,JBoss會自動發現這個包,并自動進行部署。
EJB客戶端
在本例中,你可以在同一JVM虛擬機中訪問EJB。這種設計簡化了我們的一些工作,要獲得EJB的home接口,我們需要進行一個特殊的下溯造型。列表4簡述了UserService對象,它用來訪問我們的EJB。
列表4. UserService對象
| public class UserService { private static LocalUserHome home = null; private static LocalUserHome getUserHome() throws NamingException { if (home != null) { return home; } // 取得一個上下文 InitialContext ctx = new InitialContext(); // 取得對UserEJB的一個引用 System.out.println("Looking up EJB..."); Object objRef = ctx.lookup("local/UserEJB"); // 取得UserEJB的home接口 home = (LocalUserHome)objRef; return home; } public static UserInfo getUser(String userID) { UserInfo userInfo = null; try { LocalUserHome home = getUserHome(); LocalUser localUser = null; try { System.out.println("Finding user..."); localUser = home.findByPrimaryKey(userID); } catch (FinderException e) { } if (localUser == null) { System.out.println("Creating user..."); localUser = home.create(userID,"John " + userID + " Doe","123 anywhere st.","Seattle","WA","87654"); } System.out.println("EJB returned User ID: " + localUser.getUserID()); userInfo = convertToUserInfo(localUser); System.out.println("User FullName: " + localUser.getFullName()); } catch (Exception e) { System.err.println(e.toString()); } return userInfo; } private static UserInfo convertToUserInfo(LocalUser localUser) { UserInfo userInfo; userInfo = new UserInfo(); userInfo.setId(localUser.getUserID()); userInfo.setFullName(localUser.getFullName()); userInfo.setAddress(localUser.getAddress()); userInfo.setCity(localUser.getCity()); userInfo.setState(localUser.getState()); userInfo.setZip(localUser.getZip()); return userInfo; } } |
請注意在列表4中,UserService對象首先試著通過findByPrimaryKey()方法來找到一個EJB的實例,并在這個方法中傳遞一個用戶ID的參數。如果沒有找到實體EJB的實例,UserService對象將調用EJB的home接口中的create()方法來創建一個新的EJB實例。
findByPrimaryKey()方法在調用過程中首先被EJB容器攔截,EJB容器試著從數據庫中找出一個與用戶ID相同主鍵的記錄。create()方法將在數據庫中插入一個主鍵等于用戶ID的記錄。
EJB查詢語言(EJBQL)
EJB規范提供了一種名為EJBQL的對象查詢語言,用來查詢CMP。這種對象查詢語言實際上被容器轉化為SQL語言。
EJBQL語言用來實現EJB的home接口中的find()查詢方法,并執行實體EJB定義的一些內部的select方法。這些EJBQL一般放在應用程序的部署描述文件中。
以下XML代碼(選自于jaws.xml文件)定義了在UserEJB的home接口中findByState()方法與相應EJBQL之間的映射。這個示例程序用這個EJBQL查詢語言及相應的find()方法尋找給出狀態的某個用戶。
| <entity> ... <query> <query-method> <method-name>findByState</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <ejb-ql> [!CDATA[ SELECT DISTINCT object(u) FROM UserEJB u WHERE u.STATE = ?1]] </ejb-ql> </query> </entity> |
在以上的定義當中,ejb-ql標簽定義了實際使用的EJB QL。當你只想查找一個特定類型的對象時,必須要在查詢語言中加入object關鍵字。另外這個查詢中的"?1"的作用類似于JDBC中的PrepareStatement,表明這個查詢是一個參數化查詢。如果有多個參數,則依次為"?2"、"?3"以此類推,這些參數與查詢方法findByState()中的參數表從"?1"開始一一對應。
在上例中,"?1"將被findByState()方法的第一個名為state的參數所取代。除此之外,CMP還有一個名為findAll()或findByPrimaryKey()的
內置方法,你只需要定義它們,而不需實現任何代碼,EJB容器會為你自動生成關于這兩個方法的代碼。
在列表5中,UserService類定義了一個名為getUsersByState()的方法。這個方法調用UserEJB的home接口上的findByState()方法,EJB容器會攔截這個方法,然后執行定義在jaws.xml文件中的EJBQL查詢語言。
列表5. 通過用戶狀態來查找用戶信息
| public static UserInfo[] getUsersByState(String state) { UserInfo[] users = null; // 找出所有收入高于John的職員 try { LocalUserHome home = getUserHome(); Collection userList = home.findByState(state); System.out.println("Found userList"); if (userList != null) { users = new UserInfo[userList.size()]; int i = 0; Iterator iter = userList.iterator(); while (iter.hasNext()) { LocalUser localUser = (LocalUser)iter.next(); users[i++] = convertToUserInfo(localUser); } } } catch (NamingException e) { System.err.println(e.toString()); } catch (FinderException e) { System.err.println(e.toString()); } return users; } |
由于Java樹型對象與表格型的關系數據庫中在構架上的差異,對于開發者來說,將Java對象持久化到關系型數據庫中這一工作是一件非常復雜的事情。它們之間的這種差異引發了若干種對象持久技術的產生,以使得關系型世界與對象世界之間的鴻溝日益縮小。EJB框架提供了一個基于容器管理的持久化機制,如果使用得當,將會是一種提供給開發者的優秀解決方案

