在Java虛擬機(JVM)裡面,所有數據都被建模,並且被封裝在樹結構的類和對象中。然而,在後端關系數據庫中,數據被建模成關系表,它們通過共享的鍵字段相互關聯起來。同一數據卻有兩個不同的視圖,這給企業Java的開發人員帶來了挑戰:如果你要把數據保存到持久性數據存儲區,或者從持久性數據存儲區獲取數據,就必須在對象和關系表示之間來回轉換數據,這個過程就叫作對象-關系映射(ORM)。在Java EE(Java企業版,以前叫J2EE)中,可以通過兩個方法來完成對象-關系映射。
● 人工方法:使用Java數據庫連接性(JDBC)直接處理持久性――這個簡單的解決方法適用於簡單的應用程序。JDBC API的類緊密地按照關系數據庫裡面的表、行和列進行建模。但必須在應用程序的內部對象模型和JDBC對象模型之間進行人工轉換,如果應用程序的內部模型已經類似二維關系表,采用JDBC是最佳方法。
● 自動方法:可以把ORM任務交給框架去處理。框架通常提供了可以處理任何數據對象的API。通過這個API,可以保存、獲取及查找數據庫。框架在後台完成對象-關系的轉換。因為針對特定關系的SQL查詢不適合對象接口,ORM框架通常定義了自己的查詢語言,可以為當前的關系數據庫自動生成正確的SQL語句。對數據模型復雜的應用程序而言,基於框架的方法可以節省許多時間,並且減少出錯。
ORM 框架
EJB 實體bean是Java EE中的“官方”ORM解決方案。不過在EJB1.x和2.x中,實體bean使用起來非常困難,這有兩個原因:
● EJB 1.x和2.x實體bean必須符合嚴格的組件模型。每個bean類必須實現本地接口和業務接口。它們必須從某些抽象類繼承而來,還要實現所有方法,即便許多方法是空的。有了這樣一種嚴格的組件模型,就不可能利用EJB 1.x和2.x實體bean來構建面向對象的數據模型。
● EJB 1.x和2.x容器需要極其冗長的XML配置文件把實體bean映射到關系數據庫裡面的表。那些文件非常冗長,還容易出錯。
簡而言之,EJB 1.x和2.x實體bean是一種設計拙劣的ORM框架,既滿足不了Java數據對象模型的需求,也滿足不了關系表數據模型的需求。出於對EJB 1.x和2.x實體bean的不滿,開發人員尋求ORM的其他方案。在實際環境中,采用開放源代碼的Hibernate(由JBoss公司開發)和Oracle公司的TopLink是兩個最成功的Java ORM框架。Hibernate和TopLink都基於POJO:它們不依賴任何預定義的組件模型。相反,它們獲得POJO數據對象(采用簡單的JavaBean格式)後,會自動解釋如何把這些數據對象以及它們之間的關系映射到關系數據庫。通常,一個JavaBean類映射到一張數據庫表,類之間的關系通過表裡面的外來鍵字段進行映射。可以在簡單、直觀的XML配置文件裡面指定ORM元數據,譬如與JavaBean類相對應的表名以及與屬性相對應的列名。可以通過框架中的工具類(如Hibernate中的Session類)來操作這些POJO(譬如保存、獲取及查找)。
EJB 3.0建立在 Hibernate和TopLink的思想和成功這一基礎上。它為Java EE提供了標准的POJO ORM框架。另外,較之現有的POJO持久性解決方案,EJB 3.0有兩項重要創新:
● EJB 3.0讓開發人員可以直接在POJO代碼中注釋映射信息,而不是使用XML文件來指定ORM元數據。譬如說,你可以用注釋來指定與每個JavaBean屬性相對應的關系列名。讀者會在本文後面看到更多的示例。注釋使得映射更直觀,也更容易維護。
● EJB 3.0為實體bean定義了新的存檔格式。每個存檔定義了持久性上下文,後端數據庫和ORM行為各使用獨立的一組配置。本文會在後面討論持久性上下文。
現在,我們不妨通過幾個簡單的示例來看一下EJB 3.0是如何實現POJO ORM的。
映射簡單對象
在EJB 3.0中,每個實體bean都是JavaBean樣式的簡單類。為了告訴EJB 3.0容器這個類應當進行映象以實現持久性,應當用@Entity來注釋這個類。
每個實體bean類映射到關系數據庫表。默認情況下,表名與類名相對應。可以使用@Table注釋,為該類指定另一個表名。bean類的每個JavaBean屬性映射到表中的列。默認情況下,列名就是屬性名。可以通過為屬性的設置方法添加@Column注釋,來改變這種默認關系。下面是EJB 3.0實體bean類的簡單示例:
@Entity
// @Table (name="AlternativeTableName")
public class Person implements Serializable {
protected int id;
protected String name;
protected Date dateOfBirth;
public void setId (int id) {
this.id = id; }
@Id(generate = GeneratorType.AUTO)
public int getId () {
return id; }
public void setName (String name) {
this.name = name; }
// @Column (name="AlternativeColumnName")
public String getName () {
return name; }
public void setDateOfBirth (Date dateOfBirth) {
this.dateOfBirth = dateOfBirth; }
public Date getDateOfBirth () {
return dateOfBirth; }
}
容器把Person類映射到Person SQL數據庫表後,每個Person實例就是表中的一行數據。
映射簡單的JavaBean類很容易。但需要映射相互關聯的對象時,自動ORM框架的優點才會真正體現出來。本文會在後面介紹EJB 3.0是如何處理對象關系的。
關系
在數據模型中,類與類之間通常有著關系。譬如,Person對象可以與Resume對象聯系起來,反之亦然(一對一關系);Person對象可以與多個CreditCard對象聯系起來,而CreditCard對象只與一個Person對象相對應(一對多關系)。多個Person對象可以與一個Address對象聯系起來,而一個Address對象只對應於一個Person對象(多對一關系)。
在對象模型中,對象引用負責處理這些關系。譬如說,Person對象可以有一個屬性(即字段)來引用Resume對象,還有另一個屬性是CreditCard對象的集合體。為了把對象之間的關系告訴EJB 3.0容器,只要在POJO中注釋這些JavaBean屬性。
@Entity
public class Person implements Serializable {
// ... ...
protected Resume resume;
protected CreditCard [] cards;
protected Address addr;
// ... ...
@OneToOne
public Resume getResume () {
return resume; }
// ... ...
@ManyToOne
// @JoinColumn (name="MyCustomId")
public Address getAddr () {
return addr; }
// ... ...
@OneToMany
public Collection getCards () {
return cards; }
}
在關系數據庫中,這些關系由EJB 3.0容器使用外來鍵字段自動重新構建。譬如說,Person表有一個外來鍵字段,裡面包含了Resume表中相應行的主鍵。運行時,EJB 3.0容器執行一對一的關系:它保證了Resume鍵值對Person表中的每一行來說是惟一的。為了實現Resume表到Person表的雙向查詢,也可以在Resume表中定義Person屬性,並為其添加@OneToOne注釋。
Person表中還有一個外來鍵字段,裡面包含了Address表中相應行的主鍵。這種情況下,同一個Address主鍵可出現在多個Person行中,因為這是多對一的關系。至於一對多的關系,映射起來要復雜一點,因為外來鍵的列是在多對一表中的數據源裡面定義的。所以在CreditCard類中,必須用@ManyToOne注釋來定義Person屬性。
上面討論的相互關系只是實體bean關系的一種類型,實體bean類之間的另一種重要關系是繼承。
繼承
面向對象設計的一個重要概念就是繼承。使用繼承,你可以為對象創建復雜的樹結構,而不需要重復代碼。譬如說,顧問(Consultant)是提供有償咨詢服務的人。因而在我們的數據模型中,Consultant類從具有另外收費屬性的Person類繼承而來。遺憾的是,關系數據庫裡面沒有繼承這個概念。ORM框架主要依靠這兩種方法來模仿這種行為:
● 框架可以為每個類生成單獨的表。子類的表從超類的表當中復制了所有的列。子類和超類的實例被保存在相應的表中。
● 框架可以使用包含所有子類屬性列的一張表。兩種類的實例保存在同一張表中――超類對象的行把該類沒有的列裡面的值設為空值。為了讓繼承映射更健壯,表還有“區別”列,它裡面存放的標記表明每行映射到哪個類。
EJB 3.0實體bean支持上述兩種映射策略,默認情況下采用一張表映射策略。只要注釋指明超類,即可指定繼承策略和區別列的名字。下面是Consultant類的示例,它從Person類繼承而來:
@Entity
@Inheritance(discriminatorValue="C")
@DiscriminatorColumn(name="person_type")
public class Consultant extends Person {
protected double rate;
public void setRate (double rate) {
this.rate = rate; }
public double getRate () {
return rate; }
}
在上面的例子中,容器使用默認策略將Consultant類映射到同一張表中的Person類。如果表中的person_type列的值為C,當前行就代表Consultant對象。否則,當前行代表普通的Person對象。
持久性檔案
鑒於數據模型有一組添加了注釋的EJB 3.0實體bean類,就可以把它們捆綁起來,部署到服務器環境中。EJB 3.0為實體bean定義了特殊的存檔文件格式,名為持久性存檔(文件後綴名為.par)。
.par文件是包括諸多實體bean類組成的一個jar文件,另外加上一個簡單的配置文件:META-INF/persistence.xml。persistence.XML文件定義了持久性上下文的名稱,它告訴EJB 3.0使用哪個後端數據庫(數據源)用於這一組實體bean。persistence.xml還包含了針對特定實現的配置屬性。譬如說,JBoss EJB 3.0在Hibernate 3.0上面實現。所以你可以傳遞persistence.xml文件中的任何Hibernate配置選項。下面是作為示例的persistence.XML文件,專門針對JBoss和Hibernate的配置屬性有關於SQL方言和二級緩存。
cal
Java:/DefaultDS
< property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect" />
< property name="hibernate.cache.provider_class"
value="org.jboss.ejb3.entity.TreeCacheProviderHook"/>
< property name="hibernate.treecache.mbean.object_name"
value="jboss.cache:service=EJB3EntityTreeCache"/>
實體管理器
一旦部署了實體bean, 必須通過EJB 3.0實體管理器API來訪問及操縱它們。EJB 3.0容器為每個已部署的持久性上下文(即.par文件)提供了一個實體管理器對象。可以從EJB 3.0會話bean POJO,通過@PersistenceContext注釋注入實體管理器對象,並傳入上下文的名字。
@Stateless
public class ManagerBean implements Manager {
@PersistenceContext (unitName="cal")
protected EntityManager em;
// 使用“em”
// ... ...}
基本操作
為了創建新的數據對象,並把它保存到數據庫中,只要使用Java的new關鍵字來創建POJO,並把它傳遞給EntityManager.persist()方法。
Person p = new Person ();
p.setName ("A new baby");
p.setDateOfBirth (new Date ());
em.persist (p);
為了從數據庫中獲取對象,可以使用EJB 3.0查詢語言來搜索數據庫。下面的示例演示了如何讓Person數據庫表中的所有行作為Person Java對象的集合體來返回。
// 得到所有人
Collection persons
= (Collection )em.createQuery("from Person p").getResultList();
可管理的POJO
由實體管理器保存及獲取的對象在持久性上下文中加以管理。這意味著,如果對象後來發生改變,這種改變會被自動檢測到,並且被賦予持久性、發送到數據庫中。在下面的示例中,我們更新了可管理的POJO的屬性。改變會被EJB 3.0容器自動檢測到,並發送到數據庫中。
Person p = em.find(Person.class, personId);
p.setName ("Another Name");
//當前事務結束後,p被自動更新到數據庫中。
// 沒用額外的API調用。
因為EJB 3.0實體只是POJO,它們可以進行序列化處理,並通過網絡傳遞。如果某個對象不是由容器創建(譬如它從網絡連接傳遞過來,或者是遠程過程調用的返回值),持久性上下文就不會管理它。可以通過調用EntityManager.merge()方法,把一個非管理的POJO合並到持久性上下文中。下面是把經過反序列化的POJO合並到當前持久性上下文中的示例。
InputStream in;
// 初始化輸入流
Person p = Util.deserialize (in);
// ... ...
em.merge (p);
// p現在是個可管理的對象了。p的任何改變會被自動檢測到,並被賦予持久性。
p.setName ("Another Name");
數據庫同步
實體管理器對象用於會話bean時,它與服務器的事務上下文綁在一起。服務器的事務在提交時,實體管理器提交,並且把同步內容發送給數據庫。在會話bean中,默認情況下,服務器的事務在調用堆棧末尾處提交。當然,也可以通過注釋為每個業務方法指定詳細的事務屬性。下面的示例演示了如何為會話bean方法聲明新的事務。
@TransactionAttribute(TransactionAttributeType.REQUIRESNEW)
public void update () {
//用這個方法更新Person對象;該方法結束後,所有更新被提交,並被刷新到數據庫中。
}
如果需要在事務提交前把更新內容刷新到數據庫中,可以通過顯式方式調用EntityManager.flush()方法。或者可以為方法添加@FlushMode(FlushModeType.NEVER)注釋,那樣事務管理器不會在這個方法結束時(即事務結束時)把更新內容刷新到數據庫中。這種情況下,可以手動刷新所有的數據庫更新,以實現最大程度的控制。
EJB 3.0 提供了一種簡單、有效的框架,用於把Java POJO映射到SQL數據庫中的關系表。它所采用的明智的默認映射策略基於Java類的結構和命名。不過也可以改動任何默認設置,使用簡單的一組注釋來處理復雜的對象關系。
EJB 3.0實體管理器提供了簡單的API來查找及搜索來自數據庫的對象,並為其賦予持久性。每個實體管理器對象與一組映射的POJO相關聯,並有自己的數據庫設置。它還可以自動與服務器的事務管理器綁在一起。