簡介:通過使用 EJB 2.1 以及 OpenJPA 和 EJB 3.0 中的等效功能比較 Hibernate 應用程序中的特 性和功能,學習如何將 Hibernate 應用程序源代碼、對象關系映射和配置參數遷移到 OpenJPA。
引言
Hibernate 是開放源代碼持久性和查詢框架,提供傳統 Java™ 對象 (POJO) 到關 系數據庫表的與對象相關的映射,以及數據查詢和檢索功能。Apache OpenJPA 項目將按照 EJB 3.0 Java Persistence API 規范的定義為 POJO 實體提供類似的開放源代碼持久性和查詢框架。本文介紹 Enterprise JavaBeans™ (EJB) 2.1 中的通用 Hibernate 場景,並將它們與 OpenJPA 和 EJB 3.0 中實現的等效場景進行比較。具體來說,您可以並排查看 Hibernate 應用程序源代碼、對象關系映射和 配置參數,並將它們與等效的 OpenJPA 源代碼、映射和配置進行比較。這裡顯示的比較不僅使您能夠了 解如何進行這些更改,而且說明了將使用這些通用場景的遺留 Hibernate 應用程序遷移到 OpenJPA 相當 簡單。
盡管本文重點介紹將遺留 Hibernate 應用程序遷移到 OpenJPA,但是如果您熟悉 Hibernate,還會發現其中的價值,並希望盡快使用新的 JPA 規范以及使用 OpenJPA 持久性提供程序進 行新的應用程序開發。
本文假設您熟悉 Hibernate 的基本概念,並將專門介紹 Hibernate 3.0 實現。本文中的所有示例均在 EJB 2.1 中的 Hibernate 3 中運行過,並在使用 IBM® WebSphere® Application Server V6.1 Feature Pack for EJB 3.0 的 OpenJPA 0.9.7 中運行過。
將遺留 Hibernate 應用程序遷移到 OpenJPA 的原因是多方面的。例如,Hibernate 是一個非標 准的、對象關系映射和持久性管理解決方案。Hibernate 3 需要 JDK 1.3.1 或更高版本。通過對比, OpenJPA 可實現 JPA 規范,該規范是 Java 5 規范的核心部分,並且 WebSphere Application Server V6.1 Feature Pack for EJB 3.0 的實現基於該規范。有關這些產品的詳細信息,請參見參考資料。
考慮到本文的目的,JPA 表示該規范,並且 OpenJPA 表示 JPA 規范的實現。
本文沒有介 紹 Hibernate 的所有特性和功能,但介紹了該領域中經常使用的最佳實踐。
遷移 Hibernate 應 用程序源代碼
Java Persistence API (JPA) 是作為 EJB 3.0 規范 (JSR220) 的一部分引入的, 目的是讓整個 Java 社區支持標准、單一的持久 API。JPA 將采用 Hibernate、TopLink、Java Data Objects 和 Container Managed Persistence (EJB-CMP) 2.1 規范的最佳理念。
JPA 適用於 Java Platform Standard Edition (Java SE) 和 Enterprise Edition (Java EE) 環境,因為它將實體 表示為 JPA 持久性提供程序(如 OpenJPA)可以管理的 POJO。關於實體的對象關系映射的元數據是使用 Java 5 注釋或在 XML 描述符中指定的。實體用於將 Java 對象持久保存到數據庫。
有許多 JPA 持久性提供程序。IBM 的 JPA 規范實現基於 Apache OpenJPA 項目。隨著這些 JPA 持久性提供程序的發 布,客戶現在可以對標准 API 進行編碼,不必在不兼容的非標准持久性提供程序之間進行決策。
為幫助您將遺留 Hibernate 應用程序遷移到 OpenJPA,本部分將通常使用的 Hibernate 非標准 API 與 等效的 OpenJPA 標准 API 進行了比較。本部分先比較所使用的類和接口,然後通過常規用法場景比較 API。
以下各部分提供了詳細信息:
類和接口
運行時配置
會話管理
事務管理
實體管理
分離的實體
1. 類和接口
下表將通常使用的 Hibernate 類和 OpenJPA 中的等效類進行了比較。所有的 Hibernate 類都位於 org.hibernate 包中。 所有的 JPA 接口(和 Persistence 類)都位於 javax.persistence 包中。JPA 接口的 OpenJPA 實現位 於 org.apache.openjpa.* 包中。
org.hibernate javax.persistence 說明 cfg.Configuration Persistence 配置會話工廠(在 Hibernate 中)或實體管理器工廠(在 OpenJPA 中)的引導類。通常用於為 JVM 創建單一會話(或實體管理器)工 廠。 SessionFactory EntityManagerFactory 提供 API 以打開 Hibernate 會話(或 OpenJPA 實體管理器),並處理用戶請求。通常,每個處理客戶機請求的線程都打 開一個會話(或實體管理器)。 Session EntityManager 提供 API 以便在數據庫之間存儲和加載實體。它還提供 API 以獲取事務和創建查詢。 Transaction EntityTransaction 提供 API 以管理事務。 Query Query 提供 API 以執行查詢。
2. 運行時配置
Hibernate 約定
在 Hibernate 中,運行時配置按照以下方式進行映射:
使用靜態 SessionFactory 變量。
使用 Configuration#configure() 方法。
使用 Configuration#buildSessionFactory() 方法。
清單 1. Hibernate 運行時配置
public class ORMHelper {
private static SessionFactory sf;
protected static synchronized
SessionFactory getSessionFactory(String name) {
if (sf == null) {
sf = new Configuration().configure(name).buildSessionFactory();
}
return sf;
}
...
}
使用遺留 Hibernate 應用程序時,您通常會發現一個單一的靜態 SessionFactory 實例,該實例由 JVM 中處理客戶機請求的所有線程共享。Hibernate 還可以創建多個 SessionFactory 實例,但是實際很 少這樣做。
可以通過多種方法在 Hibernate 中配置 SessionFactory。最常見的場景是調用 configure() 方法。 如果沒有向 configure() 傳入名稱,它將在類路徑的根目錄中查找 hibernate.cfg.xml。如果傳入 XML 配置文件的名稱,它將在類路徑上查找該名稱。
找到 XML 配置文件後,buildSessionFactory() 方法將使用該配置文件中的元數據創建和初始化 SessionFactory。
牢記以下事項:
有些應用程序從 JNDI 注冊表查找 SessionFactory,而不使用靜態變量,但是在第一次查找時,您仍 需要調用配置和 buildSessionFactory,因此幾乎沒有什麼效果,而且靜態變量是較常用的方法。
您還可以使用 Configuration#setProperties() 方法以編程方式配置 Hibernate 配置參數,而不使 用 configure() 方法從文件讀取這些參數,但是,較好並且頻繁使用的方法是外部化 Hibernate 屬性。
OpenJPA 約定
在 OpenJPA 中,等效運行時配置按照以下方式進行映射:
使用靜態 EntityManagerFactory 變量。
使用 Persistence#createEntityManagerFactory()
清單 2. OpenJPA 運行時配置
public class ORMHelper {
private static EntityManagerFactory sf;
protected static synchronized
EntityManagerFactory getSessionFactory(String name) {
if (sf == null) {
sf = Persistence.createEntityManagerFactory (name);
}
return sf;
}
...
}
與 Hibernate 一樣,處理 JVM 中客戶機請求的所有線程都可以使用靜態 EntityManagerFactory 實 例。如果需要多個實例,還可以定義靜態映射。
createEntityManagerFactory() 在類路徑(該類路徑包含的持久單元名稱與方法調用中指定的名稱相 同)的 META-INF 文件夾中查找 persistence.xml。如果找到的 persistence.xml 使用的持久單元與給 定名稱匹配,則 createEntityManagerFactory() 使用該文件中的元數據配置 EntityManagerFactory 實 例。如果沒有找到具有匹配名稱的 persistence.xml,則引發 javax.persistence.PersistenceException。
3. 會話管理
通常,應用程序收到客戶機請求時,將從 SessionFactory 獲取會話,並在請求結束時關閉會話,其 中請求可以是 HttpRequest 或對無狀態會話 Bean 的調用等。會話提供處理事務和從數據庫加載實體( 以及將實體存儲到數據庫)的方法。
Hibernate 應用程序通常管理該會話。為了達到目標,它們通常將會話與線程本地存儲關聯,這樣無 需將會話作為參數傳遞到需要訪問它的所有方法;相反,它們可以從線程本地存儲中檢索它。Hibernate 3.0.1 還提供了 getCurrentSession(),但是您通常會找到顯式會話管理。
就異常而言,Hibernate 3.0 會引發未經檢查的異常或運行時異常(對於 OpenJPA 也一樣),這意味 著在方法簽名中,大多數應用程序不會引發 Hibernate 異常;它們也不在自己的方法中捕獲和處理 Hibernate 異常。當然,如果需要,仍可以捕獲和處理它們。
您通常還會發現,在使用 Java SE 5 實現 OpenJPA 應用程序時,已使用 Java SE 1.4 實現了大多數 現有遺留 Hibernate 應用程序。
下面的示例使用 getSessionFactory() helper 方法獲取創建/打開會話(或實體管理器)所需的會話 工廠(或實體管理器工廠)。(有關 getSessionFactory() 方法的詳細信息,請參見運行時配置。)
Hibernate 約定
在 Hibernate 中,會話管理按照以下方式進行映射:
使用 ThreadLocal 獲取當前會話。
使用 SessionFactory#openSession() 打開會話。
使用 Session#isOpen() and Session#close() 關閉會話。
清單 3. Hibernate 會話管理
public class ORMHelper {
private static final ThreadLocal tls = new ThreadLocal();
public static void openSession() {
Session s = (Session) tls.get();
if (s == null) {
s = getSessionFactory ("test.cfg.xml").openSession();
tls.set(s);
}
}
public static Session getCurrentSession() {
return (Session) tls.get();
}
public static void closeSession() {
Session s = (Session)tls.get();
tls.set(null);
if (s != null && s.isOpen()) s.close();
}
...
}
OpenJPA 約定
在 OpenJPA 中,等效的 EntityManager 管理按照以下方式進行映射:
使用 ThreadLocal 獲取當前實體管理器。
使用 EntityManagerFactory#createEntityManager() 打開會話。
使用 EntityManager#isOpen() 和 EntityManager#close() 關閉會話。
清單 4. OpenJPA 會話管理
public class ORMHelper{
private static final ThreadLocal tls = new ThreadLocal();
public static void openSession() {
EntityManager s = (EntityManager) tls.get();
if (s == null) {
s = getSessionFactory ("test").createEntityManager();
tls.set(s);
}
}
public static EntityManager getCurrentSession() {
return (EntityManager) tls.get();
}
public static void closeSession() {
EntityManager s = (EntityManager) tls.get();
tls.set(null);
if (s != null && s.isOpen()) s.close ();
}
...
}
4. 事務管理
Hibernate 應用程序可以運行於使用不同事務策略的環境中。應用程序可以運行於使用本地 JDBC 或 全局 Java Transaction API (JTA) 事務的環境中。
使用本地 JDBC 事務是最常見的場景。如果具有異類數據存儲(如數據庫和消息隊列),則 JTA 事務 非常有用;JTA 允許您將其視為單個事務。
Hibernate 應用程序通過調用事務 API 來管理自己的事務。您使用的事務策略(JDBC 或 JTA)是在 Hibernate 配置文件中設置的,所以它與應用程序無關。
Hibernate 約定
在 Hibernate 中,事務管理按照以下方式進行映射:
使用 Session#beginTransaction() 開始事務。
使用 Transaction#commit() 提交事務。
使用 Transaction#isActive 和 Transaction#rollback() 回滾事務。
清單 5. Hibernate 事務管理
public class ORMHelper {
private static final ThreadLocal tltx = new ThreadLocal();
public static void beginTransaction() {
Transaction tx = (Transaction) tltx.get();
if (tx == null) {
tx = getCurrentSession ().beginTransaction();
tltx.set(tx);
}
}
public static void commitTransaction() {
Transaction tx = (Transaction)tltx.get();
if (tx != null && tx.isActive()) tx.commit();
tltx.set(null);
}
public static void rollbackTransaction() {
Transaction tx = (Transaction)tltx.get();
tltx.set(null);
if (tx != null && tx.isActive()) tx.rollback();
}
...
}
OpenJPA 約定
在 OpenJPA 中,等效的事務管理按照以下方式進行映射:
使用 EntityManager#getTransaction() 和 EntityTransaction#begin()。
使用 EntityTransaction#commit()。
使用 EntityTransaction#isActive() 和 EntityTransaction#rollback()。
清單 6. OpenJPA 事務管理
public class ORMHelper {
private static final ThreadLocal tltx = new ThreadLocal();
public static void beginTransaction() {
EntityTransaction tx = (EntityTransaction) tltx.get();
if (tx == null) {
tx = getCurrentSession().getTransaction();
tx.begin();
tltx.set(tx);
}
}
public static void commitTransaction() {
EntityTransaction tx = (EntityTransaction)tltx.get();
if (tx != null && tx.isActive()) tx.commit();
tltx.set(null);
}
public static void rollbackTransaction() {
EntityTransaction tx = (EntityTransaction)tltx.get();
tltx.set (null);
if (tx != null && tx.isActive()) tx.rollback();
}
...
}
盡管 OpenJPA 示例將事務存儲在 ThreadLocal 中,但通常僅調用 getCurrentSession ().getTransaction().begin(),並在以後調用 getCurrentSession().getTransaction().commit()。因 此,使用 OpenJPA,實際上不需要將事務存儲在 ThreadLocal 中。
5. 實體管理
實體管理的常見場景包括:
創建持久對象。
使用主鍵檢索持久對象。
更新持久對象。
刪除持久對象。
通常,這些場景會映射到單個用例操作,並使用分離的實體在獨立的事務中執行,但是它們還可以與 連接的實體一起使用。
Hibernate 約定
在 Hibernate 中,實體管理按照以下方式進行映射:
使用 Session#save 使臨時對象持久化。
使用 Session#load 檢索持久對象。
使用 Session#update 更新分離對象的持久狀態。(如果修改持久(連接)對象的狀態,則不需要調 用更新;在調用 commit() 時,Hibernate 自動將更新傳播到數據庫。)
使用 Session#delete 使分離或持久對象成為臨時對象。
清單 7. Hibernate 實體管理
public class ORMHelper {
...
public static void create(Serializable obj) {
getCurrentSession().save(obj);
}
public static Object retrieve (Class clz, Serializable key) {
return getCurrentSession().load(clz, key);
}
public static void update(Serializable obj ) {
getCurrentSession ().saveOrUpdate(obj);
}
public static void delete(Serializable obj ) {
getCurrentSession().delete(obj);
}
}
OpenJPA 約定
在 OpenJPA 中,等效的實體管理按照以下方式進行映射:
使用 EntityManager#persist 創建持久實體。
使用 EntityManager#find 檢索持久實體。
使用 EntityManager#merge 更新分離實體。(如果使用持久(連接)實體,則無需調用 merge; OpenJPA 將更新傳播到事務末尾的數據庫。)
使用 EntityManager#remove 刪除分離的或持久實體。
清單 8. OpenJPA 實體管理
public class ORMHelper {
...
public static void create( Serializable obj ) {
getCurrentSession().persist((Object) obj);
}
public static Object retrieve(Class clz, Serializable key) {
return getCurrentSession().find( clz, (Object) key );
}
public static Object update( Serializable obj ) {
return getCurrentSession().merge((Object) obj);
}
public static void delete( Serializable obj ) {
getCurrentSession().remove( (Object) obj);
}
}
在本示例中,ORMHelper#update() 方法的簽名已更改,因為 Hibernate update() 方法將新托管的持 久狀態復制到傳遞給該方法的分離或(臨時)對象,所以它沒有返回值。相反,在 OpenJPA merge() 方 法中,原始的分離(臨時)對象未更改,返回值包含新托管的持久狀態。
除更改 ORMHelper#update 簽名外,還必須更改調用該簽名的遺留應用程序,以便將返回值顯式分配 給原始的臨時對象。
6. 分離的實體
基於分層體系結構的 Hibernate 應用程序中另一個常見場景是,將無狀態會話 EJB 用作會話 facade 來將“分離”實體返回給 Web 層。在此場景中,您將發現會話 EJB 啟動和停止事務以響應來自 Web 層 的調用。
用於分層體系結構的這一模式的好處是,由於 EJB 層根據用戶交互來啟動和停止事務,因此在用戶執 行某項工作時,事務從不保持打開狀態。因此,所有事務的生存時間很短,應在數秒中完成。
大多數現有 Hibernate 應用程序使用 EJB 2.1 實現會話 EJB,而大多數 OpenJPA 應用程序則使用 EJB 3.0。您最初應使用資源本地實體管理器遷移到 EJB 3.0 會話 Bean,這樣不需要對事務邏輯進行更 改,但您還可以從 EJB 2.1 會話 Bean 使用 OpenJPA(請參見 通過 WebSphere Application Server V6.1 利用 OpenJPA)。遷移完成後,應考慮使用 JTA 實體管理器將應用程序重構到 EJB 3.0。
對於分離實體,還應注意:如果在一個事務中檢索對象,然後在事務外部修改該分離對象,則必須調 用該對象的更新才能將其再保存到數據庫。這是 Hibernate 和 OpenJPA 中常見的編程方法。類似地,如 果您檢索對象,並在同一事務中修改該對象,則無需調用該對象的更新,就可以將其保存到數據庫;提交 事務後,該對象會自動寫入數據庫。此方法也是 Hibernate 和 OpenJPA 的常見編程方法。
Hibernate 約定
在 Hibernate 中,EJB 2.1 的分離實體按照以下方式進行映射:
使用會話 facade 模式包裝實體。
將分離實體 (POJO) 返回到 Web 層。
清單 9. EJB2.1 中的 Hibernate 分離實體
public class CustomerFacadeBean implements SessionBean, CustomerFacade{
public Customer createCustomer( Customer customerEntity ) {
ORMHelper.openSession ();
try {
ORMHelper.beginTransaction();
ORMHelper.create (customerEntity);
ORMHelper.commitTransaction();
return customerEntity;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public Customer updateCustomer( Customer customerEntity ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
ORMHelper.update(customerEntity);
ORMHelper.commitTransaction();
return customerEntity;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public Customer getCustomer( Long customerId ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
Customer customerEntity;
customerEntity = ORMHelper.retrieve(Customer.class,customerId);
ORMHelper.commitTransaction();
return customerEntity;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public void deleteCustomer( Customer customerEntity ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
ORMHelper.delete(customerEntity);
ORMHelper.commitTransaction();
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
Throw ex;
} finally {
ORMHelper.closeSession();
}
}
...
}
OpenJPA 約定
在 OpenJPA 中,EJB 3.0 的分離實體按照以下方式進行映射:
使用會話 facade 模式包裝實體。
將分離實體 (POJO) 返回到 Web 層。
清單 10. EJB 3.0 中的 OpenJPA 分離實體
@Stateless
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class CustomerFacadeBean implements CustomerFacade {
public Customer createCustomer( Customer customerEntity ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
ORMHelper.create(customerEntity);
ORMHelper.commitTransaction();
return customerEntity;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public Customer updateCustomer( Customer customerEntity ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
Customer returnValue;
returnValue = ORMHelper.update(customerEntity);
ORMHelper.commitTransaction();
return returnValue;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction ();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public Customer getCustomer( Long customerId ) {
ORMHelper.openSession ();
try {
ORMHelper.beginTransaction();
Customer customerEntity;
customerEntity = ORMHelper.retrieve(Customer.class,customerId);
ORMHelper.commitTransaction();
return customerEntity;
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
public void deleteCustomer( Customer customerEntity ) {
ORMHelper.openSession();
try {
ORMHelper.beginTransaction();
ORMHelper.delete(customerEntity);
ORMHelper.commitTransaction();
} catch (RuntimeException ex) {
ORMHelper.rollbackTransaction();
throw ex;
} finally {
ORMHelper.closeSession();
}
}
}
清單 10 中演示了必須為遷移進行的更改。
通過比較,EJB 3.0 SessionFacade 類不能實現 SessionBean 類,也不能實現 SessionBean 的回調 方法(setSessionContext、ejbCreate、ejbRemove、ejbActivate 和 ejbPassivate)。此外,EJB 3.0 中也不需要組件接口、home 接口和部署描述符。在 EJB 2.1 部署描述符中指定的值包括在具有 Java 5 注釋的 EJB 3.0 SessionFacade 類中。
也就是說,對 SessionFacade 的業務方法沒有任何更改,預期會在這裡看到對 Hibernate 或 OpenJPA API 的大量調用。這是因為我們在 helper 類中封裝了大多數 Hibernate/OpenJPA API,並且會 話 bean 使用了 helper 類。當然,您的應用程序可能沒有構造為在 helper 類中封裝 Hibernate/OpenJPA API,但是,如果並行比較對前些部分中 helper 類進行的更改,應該能夠確定對用 於會話、事務和實體管理的 EJB 會話 bean 的更改是必需的。
遷移 Hibernate 對象關系映射
Hibernate 對象關系映射可以在啟動時加載的 XML 映射文件集中定義。可以直接使用這些映射文件或 從嵌入源代碼的 javadoc 樣式注釋中生成。在 Hibernate 的較新版本中,還可以通過 Java 5 注釋定義 對象關系映射。
可以在 XML 映射文件集中定義 OpenJPA 對象關系映射,或者通過直接嵌入代碼的 Java 5 注釋定義 它們,該對象關系映射完全不需要映射文件。
在開發中,大多數遺留 Hibernate 應用程序使用 XML 映射文件,而大多數 OpenJPA 應用程序使用 Java 5 注釋,但在生產中則將它們移動到 XML,這樣對映射的簡單更改不需要您修改源代碼和重新構建 。
由於 XML 對 Hibernate 很常見,並且在 OpenJPA 中通常用於生產,所以在本部分中對映射使用 XML 。為幫助了解對象模型 (POJO) 中哪些內容是必需的,哪些不是必需的,還包括了相應的基礎代碼(不包 括注釋)。
如果遺留 Hibernate 應用程序不使用映射文件(例如,使用 javadoc 樣式的注釋或 Java 5 注釋) ,則您仍應能夠基於本部分中的信息得出更改,並將應用程序遷移到 OpenJPA。另一方面,如果希望將 Java 5 注釋與 OpenJPA 一起使用,附錄提供了這樣的示例。
還可以使用其他方法在 Java 對象和關系表之間進行映射。本部分將介紹在企業應用程序中出現的通 用場景,其中包括:
繼承
關系
延遲初始化
對象標識
樂觀鎖定
1. 繼承
企業應用程序的數據模型通常有多個位置,類之間的一般化/專業化在這裡提供重要的重用機會。 Hibernate 和 OpenJPA 都支持可以在關系表中建模繼承的三種不同方法。我們將討論其中的兩項,我們 認為這兩項是最常見的場景:
單個表繼承
連接繼承
第三項(每個具體的類一個表)由 Hibernate 提供,但通常很少用,它是 JPA 持久性提供程序(如 OpenJPA)的可選實現。
a. 單個表繼承
對於 Java 基礎類包含其所有子類的大多數屬性的情況,可以使用單個表映射繼承,該表中的一列值 標識特定的子類,行所表示的實例屬於此類。如果沒有任何列映射到特定的子類,則這些列必須聲明為可 以為空,因為它們在該子類的數據庫行中將為空。
此繼承策略的缺點是如果子類為該實例定義多個非空屬性,則非空約束的丟失會帶來數據完整性問題 。此方法的主要優點是,它為類層次范圍的實體和查詢之間的多態關系提供最佳支持,因為不存在復雜的 連接。
對象模型
映射 1. 單個表繼承 (POJO)
// Participant (base) class
public class Participant implements Serializable {
private Long participantId;
...
}
// SalesRep (sub) class
public class SalesRep extends Participant { ... }
// Administrator (sub) class
public class Administrator extends Participant { ... }
Hibernate 約定
在 Hibernate 中,單個表繼承按照以下方式進行映射:
在基礎類中,將類與映射到表的辨別器列一起使用;還要為主鍵和其他屬性定義映射(稍後進行介紹 ,不在示例中顯示)。
將子類與子類中獨特的辨別器值一起使用;還要為子類獨有的屬性定義映射。您不能在子類中定義 ID 元素;它們沒有自已的表,因此使用(基礎)類的 ID。
映射 2. 單個表繼承(Hibernate XML 映射)
<!-- Participant (base) class -->
<class name="Participant" table="T_PRTCPNT" >
<id name="participantId" column="PRTCPNT_TID"/>
<discriminator column="PRTCPNT_TYPE" type="string"/>
...
</class>
<!-- SalesRep subclass -->
<subclass
name="SalesRep"
extends="Participant"
discriminator-value="SALES_REP">
...
</subclass>
<!-- Administrator subclass -->
<subclass name="Administrator"
extends="Participant"
discriminator-value="ADMIN">
...
</subclass>
OpenJPA 約定
在 OpenJPA 中,單個表繼承按照以下方式進行映射:
在基礎類中使用 SINGLE_TABLE 繼承策略和辨別器列;還要定義基礎類的持久屬性及其唯一的 ID。
使用子類中的辨別器值表示其實例;還要定義子類的持久屬性,而不是 ID。子類不會有任何表;它們 的屬性將提升到表示基礎類的表。
映射 3. 單個表繼承(OpenJPA XML 映射)
<entity class="Participant">
<table name="T_PRTCPNT"/>
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-column name="PRTCPNT_CLASS"/>
<attributes>
<id name="participantId">
<column name="PRTCPNT_TID"/>
</id>
...
</attributes>
</entity>
// SalesRep subclass
<entity class="SalesRep">
<discriminator-value>SALES_REP</discriminator-value>
...
</entity>
// Administrator subclass
<entity class="Administrator">
<discriminator-value>ADMIN</discriminator-value>
...
</entity>
b. 連接繼承
對於基礎類不包含所有子類的大多數屬性的情況,對於每個子類,將一個包含基礎類屬性的表與一個 單獨的連接表一起使用。對於非繼承屬性,該表僅包含列。因此,閱讀子類實例需要跨基礎類表和子類表 進行連接。
連接繼承策略的優點是您可以在子類中定義非空屬性,而對應的缺點是需要多個連接才能構造實例。 此方法也是最靈活的的方法,即您可以定義新的子類,並將屬性添加到現有子類,而無需修改基礎類表。
對象模型
映射 4. 連接繼承 (POJO)
// Participant (base) class
public class Participant implements Serializable {
private Long participantId;
...
}
// SalesRep (sub) class
public class SalesRep extends Participant {
...
}
// Administrator (sub) class
public class Administrator extends Participant {
...
}
Hibernate 約定
在 Hibernate 中,連接繼承按照以下方式進行映射:
在基礎類中,將類元素與主鍵 (id) 一起使用;還要為這些屬性定義映射,以建立基礎類。
在子類中,將連接子類與包含基礎類主鍵的外鍵列一起使用;還要在連接子類中定義其他屬性的映射 (本例中不顯示)。
映射 5. 連接繼承(Hibernate XML 映射)
<!-- Participant (base) class -->
<class name="Participant" table="T_PRTCPNT" >
<id name="participantId" column="PRTCPNT_TID"/>
...
</class>
<!-- SalesRep joined-subclass -->
<joined-subclass
name="SalesRep"
extends="Participant"
table="T_SALESREP">
<key column="PRTCPNT_TID"/>
...
</joined-subclass>
<!-- Administrator joined-subclass -->
<joined-subclass
name="Administrator"
extends="Participant"
table="T_ADMIN">
<key column="PRTCPNT_TID"/>
...
</joined-subclass>
OpenJPA 約定
在 OpenJPA 中,連接繼承按照以下方式進行映射:
在基礎類中,使用 JOINED 繼承策略。基礎類還定義所有連接子類使用的主鍵,並可以選擇定義版本 列。還要定義基礎類屬性的映射。
在子類中,定義子類的持久屬性;其表將包含這些屬性,並包含用作外鍵的主鍵列,以連接基礎類的 主鍵。子類不定義版本列。
映射 6. 連接繼承(OpenJPA XML 映射)
<!-- Participant (base) class -->
<entity class="Participant">
<table name="T_PRTCPNT"/>
<inheritance strategy="JOINED"/>
<attributes>
<id name="participantId">
<column name="PRTCPNT_TID"/>
</id>
...
</attributes
</entity>
<!-- SalesRep subclass -->
<entity class="SalesRep">
<table name="T_SALESREP"/>
<primary-key-join-column name="PRTCPNT_TID"/>
...
</entity>
<!—Administrator subclass -->
<entity class="Administrator">
<table name="T_ADMIN"/>
<primary-key-join-column name="PRTCPNT_TID"/>
...
</entity>
2. 關系
對象模型中的對象之間(和數據模型中的表之間)需要多種關系。當數據模型在類似數據類的任何列 之間未指定關系時,對象模型必須明確對象之間的關系以支持遍歷。此外,數據模型中的關系沒有任何固 有方向(盡管按一個方向搜索可以比另一個方向更有效)。而對象模型關系固有包含從一個對象到另一個 對象的方向。
對象模型關系是在數據模型中實現的,方法是通過一個表中的外鍵引用另一個表中的主鍵。具有外鍵 的表稱為子對象。其行表示對象,該對象的生命周期依賴於他們引用的對象。因此,其表將包含父對象的 外鍵。由於子對象具有外鍵,所以它必須始終指向有效的父對象;它不能是孤立的,這就意味著要刪除父 對象,必須首先刪除其子對象或從父對象到子對象執行級聯刪除。
為了維護關系,子對象也稱為關系的所有者,而父對象稱為非所有者。“所有者”這一概念非常重要 ,因為盡管 Java 程序員必須在兩邊設置雙向關系,但是數據庫只需更新一個值來反映這些更改;在子對 象(或所有者)中該更新針對外鍵。因此,在子對象中,對表示外鍵的屬性的更改會傳播到數據庫,而對 父對象中反向屬性的更改不會傳播到數據庫。
關系映射分為四個類別:
a.一對一
b.多對一
c.一對多
d.多對多
a. 一對一關系
一對一關系定義到另一個持久對象的引用,其中子對象的生命周期完全依賴於父對象的生命周期。
在對象模型中使用組件對象建模一對一關系仍將導致兩個獨立的類,但在數據模型中不會有兩個獨立 的表。.一對一關系有異常情況。如果發生異常,Hibernate 中的常見做法是將其建模為組件對象,這樣 子表中的所有屬性都將展開到父表中,因此在訪問子表的屬性時,無需連接父表和子表。
還可以使用兩個其他方法在 Hibernate 中建模一對一關系:
將多對一元素與表之間的外鍵關聯一起使用;請按照多對一關系指導原則進行操作。
將一對一元素 與表之間的主鍵關聯一起使用;使用 OpenJPA 中的一對一元素進行映射。
本部分的其余內容將介紹在 Hibernate 中使用組件元素遷移一對一關系,並介紹如何將其遷移到 OpenJPA 中的嵌入對象。
對象模型
映射 7. 一對一關系 (POJO)
// Employee (parent) class
public class Employee implements Serializable {
private Long employeeId;
private EmployeeRecord employeeRec;
...
}
// EmployeeRecord (child) class
public class EmployeeRecord implements Serializable { ... }
Hibernate 約定
在 Hibernate 中,使用組件對象的一對一關系按照以下方式進行映射:
使用類建模父類;在父類中,還要定義主鍵 (id) 和父類的其他屬性。
使用嵌套組件元素建模子類;還可以在嵌套組件中定義其他屬性(如果需要)。
映射 8. 一對一關系和組件對象(Hibernate XML 映射)
<!—Employee (parent) class
<class name="Employee" table="T_EMPLOYEE">
...
<id name="employeeId" column="EMP_TID"/>
<!-- EmployeeRecord (child) class
<component name="employeeRec" class="EmployeeRecord">
...
</component>
</class>
OpenJPA 約定
在 OpenJPA 中,使用可嵌入對象的一對一關系按照以下方式進行映射:
在父實體中聲明嵌入字段(例如 Employee)。將嵌入字段映射為父實體的數據庫記錄的一部分,並嵌 入父實體,而不是形成與子實體的關系。
將子實體(例如 EmployeeRecord)定義為可嵌入實體。
映射 9. 一對一關系和可嵌入對象(OpenJPA XML 映射)
<!-- Employee (parent) class -->
<entity class="Employee">
<table name="T_EMPLOYEE"/>
<attributes>
<id name="employeeId">
<column name="EMP_TID"/>
</id>
<embedded name="employeeRec"/>
...
</attributes>
</entity>
<!-- EmployeeRecord (child) class -->
<embeddable class="EmployeeRecord">
...
</embeddable>
b. 多對一關系
多對一關系定義到單個持久對象的引用。盡管多對一關系可以是單向的,但是經常將其定義為一對多 雙向關系的反向過程。
聲明多對一關系的實體是子對象(或關系的所有者),因為其表包含外鍵,而聲明多對一關系的實體 引用的對象是父對象。由於其表不包含外鍵,所以它是非所有者或關系的反向。
對象模型
映射 10. 多對一關系 (POJO)
// Address (parent) class
public class Address implements Serializable {
private Long addressId;
...
}
// Phone (child) class
public class Phone implements Serializable {
private Address address;
...
}
Hibernate 約定
在 Hibernate 中,多對一關系按照以下方式進行映射:
在子類中使用多對一元素。
在父類中定義主鍵。
映射 11. 多對一關系(Hibernate XML 映射)
<!-- Phone (child) class -->
<class name="Phone" table="T_PHONE">
<many-to-one
name="address"
class="Address"
column="ADDR_TID">
</many-to-one>
...
</class>
<!-- Address (parent) class -->
<class name="Address" table="T_ADDRESS">
<id name="addressId" column="ADDR_TID"/>
...
</class>
OpenJPA 約定
在 OpenJPA 中,多對一關系按照以下方式進行映射:
在父實體中定義主鍵 (id)。
在子實體中使用多對一元素定義關系,並使用嵌套連接列元素定義外鍵。連接列指定如何按照連接的 方法找到此子實體的父實體。
映射 12. 多對一關系(OpenJPA XML 映射)
<!-- Address (parent) class -->
<entity class="Address">
<table name="T_ADDRESS"/>
<attributes>
<id name="addressId">
<column name="ADDR_TID"/>
</id>
...
</attributes>
</entity>
<!-- Phone (child) class -->
<entity class="Child">
<table name="T_PHONE"/>
<attributes>
<many-to-one name="address">
<join-column name="ADDR_TID"/>
</many-to-one>
...
</attributes>
</entity>