程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 將遺留Hibernate應用程序遷移到OpenJPA和EJB 3.0(二)

將遺留Hibernate應用程序遷移到OpenJPA和EJB 3.0(二)

編輯:關於JAVA

c. 一對多關系

一對多關系定義到對象集合的引用。由於用例通常需要從父對象到子對象的遍歷,而可能需要(也可 能不需要)從子對象到父對象的遍歷,所以一對多關系是對象模型中最常見的關系類型;這意味著單向一 對多關系可以滿足大多數情況。

也就是說,如果用例需要從子對象到父對象的遍歷,則可以在子類中方便地添加多對一關系,使之成 為雙向關系。

聲明一對多關系的實體是父對象(並且是非所有者)。此實體的表定義主鍵,但是它沒有外鍵(外鍵 在子對象中)。

此一對多關系引用的對象是子對象和關系的所有者。子對象具有外鍵,並引用父對象的主鍵。

在 Hibernate 中,一對多關系的映射通常是通過將列添加到外鍵的子表完成的,但映射的詳細內容是 不同的,具體取決於是單向一對多關系,還是雙向一對多關系。

在單向關系中,子表中的外鍵列不會映射到子對象中的屬性,它在數據模型中,而不是在對象模型中 。由於是單向的,所以僅在父對象中有屬性,而子對象中沒有。此外,必須將外鍵列定義為可以為空,因 為 Hibernate 將首先插入子行(使用 NULL 外鍵),並在以後更新它。

在雙向關系中,對象關系映射比較好,因為子對象中有一個用於外鍵列的屬性,在數據庫中該列不必 為空。但是,結果對象模型在對象之間有循環依賴關系和更緊密的耦合關系,並需要其他編程來設置關系 的兩端。

可以看出,關於一對多關系的定義,有多個要考慮的權衡因素,但是通常建議使用單向關系,除非用 例指示需要用兩個方向導航。

對象模型

映射 13. 一對多關系 (POJO)

// Address (parent) entity
public class Address implements Serializable {
   private Long addressId;
  private Set phones = new HashSet();
  ...
}
// Phone (child) entity
public class Phone implements Serializable {
  ...
}

Hibernate 約定

在 Hibernate 中,一對多(單向)關系按照以下方式進行映射:

在父類中,將設置、包或列表與一對多子元素一起使用。

如果關系是單向的,則在表示子類的表中創建外鍵;否則,使用雙向關系的多對一關系。

映射 14. 一對多關系(Hibernate XML 映射)

<!-- Address (parent) class -->
<class name="Address" table="T_ADDRESS">
  <id name="addressId" column="ADDR_TID"/>
  <set
   name="phones"        
   table="T_PHONE"
   cascade="all-delete- orphan">
   <key column="ADDR_TID"/>
   <one-to-many class="Phone">
  </set>
</class>
<!-- Phone (child) class -- >
<class name="Phone" table="T_PHONE">
  ...
</class>

務必注意 Hibernate 中的專有 cascade="all-delete-orphan" 功能(請參見映射 14)。使用此屬性 使您能夠從數據庫刪除子對象,方法是直接從父集合中移除它,然後將父對象保存到數據庫。使用此專有 功能,不在代碼中顯式刪除子對象。盡管在標准 JPA 中沒有任何等效功能,但 OpenJPA 中的專有 @ElementDependent 注釋提供了自動孤立項清除功能,但是此功能不可移植到其他持久性提供程序,並且 在取讀代碼時可導致混淆。接下來將詳細討論 all-delete-orphan 功能。

OpenJPA 約定

在 OpenJPA 中,您無法使用外鍵映射一對多單向關系,您必須使用連接表映射它。也就是說,如果關 系是雙向的,則可以使用外鍵映射。這樣,有兩個選項可以從 Hibernate 映射一對多單向關系:

使用連接表映射並將連接表添加到數據庫,或

使用外鍵映射並將關系從單向轉換為雙向,將反向屬性添加到對象模型,並更改代碼,以便將關系設 置為兩個方向。

建議將單向關系轉換為雙向關系,因為與改變現有數據庫模式和關聯的行相比,此方法通常更容易修 改遺留代碼。

在 OpenJPA 中,一對多(雙向)關系按照以下方式進行映射:

在父對象中,使用一對多元素定義子對象的集合,並使用 id 元素為父對象定義主鍵。

在子類中,使用多對一元素定義父對象,並使用嵌套連接列元素定義子對象中的外鍵,並指定如何連 接父對象和子對象。

映射 15 一對多關系(OpenJPA XML 映射)

<!-- Address (parent) class -->
<entity class="Address">
  <table name="T_ADDRESS"/>
  <attributes>
   <id name="addressId">
     <column name="ADDR_TID"/>
   </id>
    <one-to-many name="phones" mapped-by="address">
     <cascade>
       <cascade-all/>
     </cascade>
   </one-to- many>
   ...
  </attributes>
</entity>
<!-- Phone (child) class -->
<entity class="Phone">
  <table name="T_PHONE"/>
  <attributes>
   <many-to-one name="address">
     <cascade>
      <cascade-all/>
     </cascade>
     <join-column name="ADDR_TID"/>
   </many-to-one>
   ...
  </attributes>
</entity>

在定義一對多關系時,通常使用一些其他功能,您需要知道如何遷移這些功能:

Hibernate

inverse="true"

在 Hibernate 中,您可能會遇到在定義雙向關系時使用的 inverse="true" 屬性。如果是這樣,請不 要擔心,因為此功能等效於 OpenJPA 指定的關系的非所有者(它的表沒有外鍵)。類似地,Hibernate inverse="false" 屬性等效於 OpenJPA 的關系的所有者(它的表有外鍵)。

通常,這些概念是一致的,但使用 inverse="true" 設置在雙向關系的多對一端定義 Hibernate 映射 這一情況除外。如果發現此類映射,則應在執行遷移之前更改它,因為它在 Hibernate 中不是最佳實踐 ,並且不能生成最佳的 SQL。例如,如果將多對一端設置為 inverse="true",則每次創建子對象時, Hibernate 將執行兩個 SQL 語句,一個創建子對象,一個使用父對象的外鍵更新子對象。在多對一端將 其更改為 inverse="false" 並在一對多端設置 inverse="true" 可以解決這一問題,並使它與 OpenJPA 保持一致。當然,進行該更改後,您應重新測試應用程序。

cascade="all-delete-orphan"

JPA 規范中沒有與此 Hibernate 功能等效的功能,但是 OpenJPA 持久性提供程序中的專有 @ElementDependent 注釋完全可以執行 Hibernate 的 all-delete-orphan 功能執行的任務。如果需要使 用該功能,請參閱 OpenJPA 用戶指南。盡管它是專有功能,但是,如果使用它遷移 all-delete-orphan 特性,則不需要對應用程序源代碼進行任何更改。

若要以符合標准的方式遷移 all-delete-orphan 功能,您可以使用 OneToMany 注釋中的 cascade=CascadeType.ALL 屬性,並更改源代碼,不僅從父集合中移除子對象,而且從數據庫中顯式刪除 子對象;例如,不需要類似於以下的代碼:

public class Address implements Serializable {
  ...
  public void removePhone(Phone p) {
   this.phones.remove(p); // Explicitly remove p from the set; which
               // Implicitly deletes p from the database
  }
}

您應使用類似於以下的代碼替換它:

public class Address implements Serializable {
  ...
  public void removePhone(Phone p) {
   // Explicitly remove p from the set
    this.phones.remove(p);
   
   // Explicitly delete p from the database
    ORMHelper.getCurrentSession().delete(p);
  }
}

OpenJPA

cascade=CascadeType.ALL

盡管上面的 Hibernate 示例僅指定了從父對象到子對象的級聯操作,但是在使用 OpenJPA 持久性提 供程序測試此映射時,我們必須將 cascade=CascadeType.ALL 定義為兩個方向,並在 OneToMany 注釋和 ManyToOne 注釋中指明。通常,您不需要從子對象到其父對象的級聯操作(在 ManyToOne 注釋中),但 是這對於使從父對象到子對象的 merge() 級聯操作能夠工作是必需的。

如果不按兩個方向定義級聯操作,則 remove() 和 persist() 的級聯操作將從父 Address 對象到其 依賴的 Phone 對象進行,但是 merge() 的級聯操作會引發異常,指示依賴對象中的 Phone.address 字 段不允許級聯。

(這是當前正在使用的 OpenJPA v0.9.7 中的已知問題,在以後的版本中會修復該問題。與此同時, 解決辦法是按兩個方向啟用級聯。)

d. 多對多關系

多對多關系通過映射表定義到對象集合的引用。在對象模型中,多對多關系並不是全部通用的,但是 它們通常是雙向的。

與其他雙向關系一樣,存在所屬端和非所屬端。在這種情況下,所屬端擁有映射表,而不是外鍵。可 以將任意一端指定為所屬端;選取哪一端並不重要。

對象模型

映射 16. 多對多關系 (POJO)

// Group (non-owner/parent) entity
public class Group implements Serializable {
  private Long groupId;
  private Set users = new HashSet();
  ...
}
// User (owner/child) entity
public class User implements Serializable {
   private Long userId;
  private Set groups = new HashSet();
  ...
}

Hibernate 約定

在 Hibernate 中,多對多關系按照以下方式進行映射:

非所有者將集合(設置、包或列表)元素與 inverse="true" 屬性、表屬性、鍵子元素和多對多子元 素一起使用。

關系所有者將集合(設置、包或列表)元素與表屬性、鍵子元素和多對多子元素一起使用。

映射 17. 多對多關系(Hibernate XML 映射)

<!—Group (non-owner/parent) class -->
<class name="Group" table="T_GROUP">
  <id name="groupId" column="GROUP_TID"/>
  <set name="users" table="T_USER_GROUP" inverse="true">
    <key column="GROUP_TID"/>
    <many-to-many column="USER_TID" class="User"/>
  </set>
</class>
<!—User (owner/child) class -->
<class name="User" table="T_USER">
  <id name="userId" column="USER_TID"/>
   <set name="groups" table="T_USER_GROUP">
    <key column="USER_TID"/>
    <many-to-many column="GROUP_TID" class="Group"/>
   </set>
</class>

OpenJPA 約定

在 OpenJPA 中,多對多關系按照以下方式進行映射:

在所有者/子對象中,使用多對多元素和嵌套連接表元素指定如何創建關系。您還將 id 與嵌套列元素 一起使用,以指定連接表引用的主鍵名稱。

在非所有者/父對象中,將多對多元素與 mapped-by 屬性一起使用,以引用聲明連接表的類中的屬性 。(連接表包含創建關系的所有信息。您還需要通過 id 元素創建主鍵。)

映射 18. 多對多關系(OpenJPA XML 映射)

<!-- User (owner/child) class -->
<entity class="User">
  <table name="T_USER"/>
  <attributes>
   <id name="userId">
     <column name="USER_TID"/>
   </id>
   <many-to-many name="groups">
     <join-table name="T_USER_GROUP">
       <join-column name="USER_TID"/>
      <inverse-join-column name="GROUP_TID"/>
     </join-table>
   </many-to-many>
    ...
  </attributes>
</entity>
<!-- Group (non-owner/parent) class -->
<entity class="Group">
  <table name="T_GROUP"/>
  <attributes>
   <id name="groupId">
     <column name="GROUP_TID"/>
   </id>
   <many-to-many name="users" mapped- by="groups"/>
   ...
  </attributes>
</entity>

3. 延遲初始化

可以將延遲初始化應用到任何字段,但是它經常與一對多或多對多關系一起使用,在讀取父對象時, 您還可以控制是否需要數據庫返回所有子對象。

為進一步闡述此概念,請考慮使用從 A 到 B 的一對多或多對多關系。如果在該關系中將 fetch 設置 為 LAZY,則從數據庫讀取 A 將不能從該數據庫讀取 B,直到代碼嘗試遍歷從 A 到 B 的關系。另一方面 ,如果將 fetch 設置為 EAGER,則從數據庫讀取 A 也可以從該數據庫讀取依賴的 B 對象。

在使用 LAZY 或 EAGER 的 fetch 定義關系時,請務必小心,尤其是在按照一般模式,EJB 層將分離 實體返回到 Web 層並且 Web 層訪問該數據以呈現視圖時更要小心。使用 LAZY 加載,Web 層可以沒有呈 現視圖必需的依賴對象。另一方面,您不能使用 EAGER 直接加載所有關系,因為每次返回的信息比必要 信息多。

原因在於您必須使用滿足業務需求的適當獲取策略定制域模型。如果用例需要父對象和子對象,則使 用 EAGER 加載。如果用例不需要子對象,則使用 LAZY 加載。

對象模型

映射 19. 延遲初始化 (POJO)

// Address (parent) entity
public class Address implements Serializable {
   private Long addressId;
  private Set phones = new HashSet();
  ...
}
// Phone (child) entity
public class Phone implements Serializable {
  private Address address;
  ...
}

Hibernate 約定

在 Hibernate 中,缺省值是用於集合的延遲初始化,它實現一對多或多對多關系。要禁用延遲(或啟 用 eager)初始化,請按照以下方式進行映射:

在父對象中,將集合(設置、包或列表)與 lazy=false 屬性一起使用。

在子對象中,將類與 lazy=false 屬性一起使用以啟用獲取。

映射 20. 延遲初始化(Hibernate XML 映射)

<!-- Address (parent) class -->
<class name="Address" table="T_ADDRESS">
  ...
  <id name="addressId" column="ADDR_TID"/>
  <set
    name="phones"        
    table="T_PHONE"
     cascade="all-delete-orphan"
    inverse="true"
    lazy="false">
     <key column="ADDR_TID"/>
    <one-to-many class="Phone"/>
  </set>
</class>
<!-- Phone (child) class -->
<class name="Phone" table="T_PHONE" lazy="false">
  ...
  <many-to-one
    name="address"
   class="Address"
   column="ADDR_TID">
  </many-to- one>
</class>

OpenJPA 約定

在 OpenJPA 中,一對多和多對一關系的延遲初始化也是缺省值。要禁用延遲初始化(和啟用 eager 初始化),請按照以下方式進行映射:

在父實體的集合中使用 fetch=FetchType.EAGER 屬性。

映射 21. 延遲初始化(OpenJPA XML 映射)

<!-- Address (parent) class -->
<entity class="Address">
  <table name="T_ADDRESS"/>
  <attributes>
   <id name="addressId">
     <column name="ADDR_TID"/>
   </id>
    <one-to-many name="phones" mapped-by="address" fetch="EAGER">
     <cascade>
      <cascade-all/>
     </cascade>
    </one-to-many>
   ...
  </attributes>
</entity>
<!-- Phone (child) class -->
<entity class="Phone">
  <table name="T_PHONE"/>
  <attributes>
   <many-to-one name="address">
     <cascade>
      <cascade-all/>
     </cascade>
     <join-column name="ADDR_TID"/>
   </many-to- one>
   ...
  </attributes>
</entity>

還值得一提的是,Hibernate 和 OpenJPA 在從分離對象訪問延遲加載的集合上是不同的。在 Hibernate 中,如果程序員嘗試訪問分離對象上延遲加載的集合,則會引發異常;而 OpenJPA 將返回空 值,而不是異常。

此差異的原因是 JPA 規范沒有指定如何處理在分離對象上訪問延遲加載的集合。每個 JPA 供應商可 以決定如何處理此條件。它們會引發異常,或者將其保留為未初始化狀態,甚至返回具有零個元素的集合 。

因此,如果遺留 Hibernate 應用程序正在使用異常檢測對分離對象的延遲加載集合的訪問,您可以使 用 OpenJPA 通過測試空集合執行相同的操作。不過,需要記住的是:JPA 規范沒有說明是引發異常還是 返回空值,因此依賴於此行為不可移植,並且隨時會更改,甚至可能在以後版本中中斷您的應用程序。

此外,還要務必注意您是獲取異常(在 Hibernate 中獲取),還是不獲取異常(在 OpenJPA 中不獲 取),異常可以指示在測試過程中需要檢測和修復的應用程序錯誤。也就是說,問題在於您是否獲取異常 ,所以在 JPA 規范中強制引發異常與否不能解決問題:為了使用分離對象,應用程序需要某些體系結構 指導原則。

有以下三個解決方案使用分離對象:

在返回視圖所需的所有集合之前,請顯式初始化它們;即您應確定延遲加載是否適用於這些特定的關 系。

保持實體管理器處於打開狀態,直到完成呈現視圖(即在視圖中打開和關閉實體管理器),這可以全 部避免使用分離實體。

與第二個解決方案類似,EJB 3.0 的第三個可能的解決方案使用擴展的持久上下文,以保持實體管理 器在事務之間處於活動狀態。

如果將實體從 EJB 組件傳遞到 Web 層,則應遵循第一個解決方案,因為它使您能夠保持 EJB(會話 facade)中的事務邏輯,而不是需要 Web 層管理該事務。此外,Web 層難於管理事務,因為它需要提交 事務,並在呈現視圖後關閉實體管理器(可能在 ServletFilter 中)。

關於分離對象的最後一點是您應該讓對象模型符合業務用例。如果某些用例需要集合的延遲加載,而 其他用例需要該集合的 eager 加載,則在對象模型中使用集合的延遲加載,並根據需要在會話 facade 中強制執行 eager 加載。即,在會話 facade 中為不同的用例定義不同的方法:一種方法僅返回分離對 象,而另一種方法加載子對象,然後返回分離的對象。共有三種方法可加載 OpenJPA 中的子對象;前兩 種方法為 JPA 標准,最後一種方法為 OpenJPA 擴展:

通過調用集合上的 size(),進行集合的 Trigger 加載。

將 JP-QL 與 Fetch Join 功能一起使用,臨時覆蓋 Lazy fetch 類型。

使用 OpenJPA FetchGroups 注釋加載子對象。

4. 對象標識

數據模型中的所有表與表的主鍵一樣包括稱為 OID 的對象標識列。OID 的最佳實踐是使用其值由系統 分配的整數列。在這種情況下,OID 使用 java.lang.Long 映射到對象模型的主鍵。同時,表(外鍵)之 間的所有關系也都基於關系表的 OID 列。

在使用通常的 Hibernate Sequence 生成器類插入任何新行時,Hibernate 會分配 ID,它使用基礎數 據庫支持序列,其中每個表都有一行包含表名稱和當前 OID 號。在此方法中,OID 對每個表都是唯一的 (OID 在表之間可能不是唯一的)。

使用 OID 為沒有業務含義的所有數據提供鍵。這些系統生成的鍵沒有任何業務含義,被稱為人工鍵, 而不是具有業務含義的自然鍵。使用人工鍵可以更改數據庫中任何具有業務含義的數據,而不用擔心違反 約束。

在現有 Hibernate 模型中,您通常還會發現大多數表具有可以包括許多列的具有業務含義的標識。您 可能還會發現在數據模型中的任何此類候選主鍵上定義了唯一約束。此數據庫約束可確保唯一性,並且還 基於業務含義為查找此類實體提供索引。

您可能會遇到某些特殊情況,其中數據必須預填充到某些表,以表示沒有任何業務含義的數據行(對 於代碼表和類似內容)。為此,在啟動應用程序之前,必須將這些行插入到數據庫,這些行的 OID 必須 小於用於該表的 SEQUENCE 表的起始值。在這種情況下,您可能會發現初始值大於 1 的序列表。

對象模型

映射 22. 對象標識 (POJO)

/ Address entity
public class Address implements Serializable {
  private Long addressId;
  ...
}

Hibernate 約定

在 Hibernate 中,對象標識按照以下方式進行映射:

將 id 與嵌套生成器子元素一起使用。

映射 23. 對象標識(Hibernate XML 映射)

<class name="Address">
  ...
  <id name="addressId" column="ADDR_TID">
    <generator class="sequence">
      <param name="sequence">T_ADDRESS_SEQ</param>
    </generator>
   </id>
  ...
</class>

OpenJPA 約定

在 OpenJPA 中,同一對象標識按照以下方式進行映射:

使用 Id、SequenceGenerator 和 GeneratedValue 注釋。

映射 24. 對象標識(OpenJPA XML 映射)

/<!-- Address entity -->
<entity class="Address">
 <sequence -generator name="AddressSeq" sequence-name="T_ADDRESS_SEQ"/>
 <attributes>
  <id name="addressId">
   <column name="ADDR_TID"/>
    <generated-value strategy="SEQUENCE" generator="AddressSeq"/>
  </id>
   ...
 </attributes>

對於 Hibernate 和 OpenJPA,OID 的現有序列表的 DDL 為:

create sequence T_ADDRESS_SEQ;

關於對象標識符的最後一點說明:數據模型中的所有外鍵通常基於關系表的 OID 主鍵。所有外鍵在引 用主鍵的數據模型中都具有約束,並定義了 ON DELETE RESTRICT 語義,這樣,如果仍然存在該父對象的 子對象的行,則數據庫可以防止您從該數據庫刪除行(例如,您無法刪除父對象)。

5. 樂觀鎖定

Hibernate 為並發控制提供樂觀和悲觀鎖定模式。為獲得短時間的事務和較好的性能,大多數遺留 Hibernate 應用程序使用樂觀鎖定。當用戶編輯屏幕上的數據時,短時間的事務會釋放鎖定。樂觀鎖定會 檢查對象的時間戳或版本,以確保當用戶編輯數據時,沒有更改該對象。如果更改,則引發樂觀鎖定異常 ,通知用戶已發生異常,並且用戶可以刷新導致異常的數據,並重試事務,以便下一次正常運行。通知用 戶樂觀鎖定異常通常通過應用程序的常規異常處理發生。

對象模型

並發性會導致問題,所有實體將包含版本屬性,持久性提供程序使用此屬性檢測同一記錄的並發修改 。遺留應用程序只是將版本屬性視為不可改變,並且在從數據庫讀取實體時,負責將該版本屬性保存在實 體中,直到將實體寫回數據庫。

映射 25. 對象標識 (POJO)

// Address entity
public class Address implements Serializable {
  private Long version;
  ...
}

Hibernate 約定

在 Hibernate 中,樂觀鎖定按照以下方式進行映射:

使用版本元素定義版本屬性。

映射 26. 樂觀鎖定(Hibernate XML 映射)

<!-- Address class -->
<class name="Address" table="T_ADDRESS">
  ...
  <version name="version" column="VERSION"/>
  ...
</class>

OpenJPA 約定

在 OpenJPA 中,樂觀鎖定按照以下方式進行映射:

使用版本元素定義版本屬性。

映射 27. 樂觀鎖定(OpenJPA XML 映射)

<!-- Address class -->
<entity class="Address">
  <table name="T_PHONE"/>
  <attributes>
   ...
   <version name="version">
     <column name="VERSION"/>
   </version>
   ...
  </attributes>
</class>

Hibernate 支持樂觀和悲觀鎖定,但是 JPA 規范僅定義樂觀鎖定的概念。不過,OpenJPA 支持樂觀鎖 定,並為悲觀鎖定提供擴展。因此,如果遷移使用悲觀鎖定的遺留 Hibernate 應用程序,請考慮在 persistence.xml 文件中設置以下 OpenJPA 配置屬性:

映射 28. 悲觀鎖定(OpenJPA 配置屬性)

<property
  name="openjpa.LockManager"
  value="pessimistic">
</property>
<property
  name="openjpa.ReadLockLevel"
  value="read">
</property>
<property
  name="openjpa.WriteLockLevel"
  value="write">
</property>
<property
  name="openjpa.jdbc.TransactionIsolation"
  value="repeatable- read">
</property>

使用這些屬性,所有讀取內容都獲取共享(讀取)鎖定並保存它們,直到事務結束。如果在同一記錄 具有多個並發更新版本時遇到並發性問題(死鎖),則可能需要指定寫入的 ReadLockLevel,以便在檢索 數據時生成 FOR UPDATE,並強制執行更新版本的序列。

如果使用配置參數在 persistence.xml 文件中指定這些悲觀鎖定級別,則它們將會應用於所有事務。 另外,您可能需要使用 org.apache.openjpa.persistence.FetchPlan 類以編程方式為單個事務設置鎖定 級別,如以下代碼片段所示:

映射 28. 悲觀鎖定(以編程方式設置的 OpenJPA 配置屬性)

import org.apache.openjpa.persistence.*;
...
EntityManager em = ...;
em.getTransaction ().begin ();
...
// load an object with a pessimistic read lock mode
fetch = ( OpenJPAPersistence.cast( em ) ).getFetchPlan ();
fetch.setReadLockMode( LockModeType.WRITE );
Address address = em.find( Address.class, addressId );
...
em.getTransaction ().commit ();

遷移 Hibernate 配置參數

在 Hibernate 中,配置 SessionFactory 的最常見方法是在 hibernate.cfg.xml 文件中包括 <property> 元素,並將該文件放置在類路徑的根文件夾中。另一個很少使用的等效方法是在類路 徑的 hibernate.properties 文件中包括屬性。

在 OpenJPA 中,EntityManagerFactory 是通過以下方法配置的:在 persistence.xml 文件中包括命 名的 <persistence-unit> 元素,並將該文件放置在類路徑的 META-INF 文件夾中。 <persistence-unit> 定義持久性提供程序、映射文件和其他屬性,如數據庫連接和日志記錄。持 久性提供程序標識實現 JPA 規范的供應商,<persistence-unit> 中的命名屬性將特定於持久性提 供程序(本示例中為 OpenJPA)。

將 Hibernate 應用程序遷移到 OpenJPA 時,至少會遇到三個常見配置場景:

數據庫連接——該配置屬性告訴 SessionFactory 如何連接到數據庫。

映射位置——該屬性控制對象到數據庫中行的映射。

日志類別——該屬性使您能夠診斷問題,如設置日志記錄/跟蹤級別。

您可能會遇到許多配置屬性,這裡無法一一介紹,所以請一定要參閱參考資料,獲得關於映射其他配 置屬性的信息。其中特別重要的參考資料是用於 org.hibernate.cfg.Environment 類和所有 Hibernate 配置屬性的 Hibernate API 文檔和 OpenJPA 用戶指南。

1. 數據庫連接

有以下兩種方法配置數據庫連接:使用本地 JDBC 連接(這裡將介紹它)或使用 J2EE 數據源(請參 見參考資料)。

Hibernate 約定

在 Hibernate 中,配置 JDBC 連接按照以下方式進行映射:

使用 dialect 配置參數。

使用 connection.driver_class 配置參數。

使用 connection.url 配置參數。

使用 connection.username 配置參數。

使用 connection.password 配置參數。

配置 1. Hibernate 數據庫連接

...
<hibernate-configuration>
  <session-factory>
   <property
     name="dialect">
     org.hibernate.dialect.DB2Dialect
   </property>
  <property
     name="connection.driver_class">
     com.ibm.db2.jcc.DB2Driver
    </property>
  <property
     name="connection.url">
     jdbc:db2://localhost:50000/HIBTEST
   </property>
  <property
      name="connection.username">
     db2admin
   </property>
   <property
     name="connection.password">
     db2admin
    </property>
  </session-factory>
</hibernate-configuration>

OpenJPA 約定

在 OpenJPA 中,配置等效 JDBC 連接按照以下方式進行映射:

使用 openjpa.jdbc.DBDictionary 配置參數。(此參數是可選的,因為 OpenJPA 通常可以通過 URL 和 DriverName 屬性確定正確的字典。)

使用 openjpa.ConnectionDriverName 配置參數。

使用 openjpa.ConnectionURL 配置參數。

使用 openjpa.ConnectionUserName 配置參數。

使用 openjpa.ConnectionPassword 配置參數。

配置 2. OpenJPA 數據庫連接

<persistence ...>
  <persistence-unit name="JPATEST">
    <properties>
    <property
       name="openjpa.jdbc.DBDictionary"
      value="db2(DriverVendor=db2)" >
      </property>
     <property
       name="openjpa.ConnectionDriverName"
      value="com.ibm.db2.jcc.DB2Driver">
     </property>
     <property
       name="openjpa.ConnectionURL" 
       value="jdbc:db2://localhost:50000/JPATEST">
     </property>
     <property
      name="openjpa.ConnectionUserName"
       value="db2admin">
     </property>
    <property
       name="openjpa.ConnectionPassword"
      value="db2admin">
     </property>
   </properties>
  </persistence- unit>
</persistence>

(盡管此示例不需要另一個參數 openjpa.ConnectionProperties,但它非常有用,在執行 connect() 調用時,它允許傳入其他屬性。)

2. 映射位置

如果您在 Hibernate 中使用基於 XML 的配置方法,則不僅可以指定配置參數,而且可以指定映射文 件的位置。這是常見的場景,因為它使您能夠配置 SessionFactory,而無需以編程方式指定映射文件的 位置。

Hibernate 約定

在 Hibernate 中,配置映射文件的位置按照以下方式進行映射:

將 <mapping> 與資源屬性一起使用。

配置 3. Hibernate 映射位置

...
<hibernate-configuration>
<session-factory>
...
<!-- Mapping files -->
<mapping resource="domainmodel.hbm.xml"/>
</session-factory>
</hibernate-configuration>

OpenJPA 約定

在 OpenJPA 中,配置映射文件的位置按照以下方式進行映射:

使用 <mapping-file> 元素。

配置 4. OpenJPA 映射位置

<persistence ...>
<persistence-unit name="TEST">
...
<!-- Mapping files -->
<mapping-file>META-INF/orm.xml</mapping-file>
</persistence-unit>

3. 日志類別

Hibernate 應用程序的故障排除非常困難,因為 Hibernate 會基於映射文件中指定的元數據為您生成 SQL 命令。因此,將日志類別配置為查看 Hibernate 提交到數據庫的准確 SQL 通常非常有用。

Hibernate 約定

在 Hibernate 中,日志類別的配置按照以下方式進行映射:

使用 show_sql 配置參數輸出 SQL 命令。

配置 5. Hibernate 日志類別

...
<hibernate-configuration>
<session-factory>
...
<property
name="show_sql">
true
</property>
</session-factory>
</hibernate-configuration>

如果研究 SQL 不足以診斷問題,Hibernate 中還提供了其他日志類別,但是您將發現在類路徑的 log4j.properties 文件中配置了日志類別。(此處不介紹該文件的配置;請參考 Hibernate 文檔,獲得 關於配置其他 Hibernate 日志類別的信息。)

OpenJPA 約定

在 OpenJPA 中,日志記錄的配置按照以下方式進行映射:

使用 openjpa.Log 配置屬性輸出 SQL。

使用 openjpa.ConnectionFactoryProperties 准確地輸出 SQL。

配置 6. OpenJPA 日志類別

<persistence ...>
<persistence-unit name="TEST">
...
<properties>
<property
name="openjpa.Log"
value="SQL=TRACE">
</property>
<property
name="openjpa.ConnectionFactoryProperties"
value="PrettyPrint=true, PrettyPrintLineLength=72">
</property>
</properties>
</persistence-unit>

您還可以使用 openjpa.Log 配置屬性將 OpenJPA 配置為輸出其他日志信息。(請參見 OpenJPA 用戶 指南。)

結束語

本文詳細介紹了將使用 EJB 2.1 的專有 Hibernate 3 應用程序遷移到使用 OpenJPA 0.9.7 持久性提 供程序和 EJB 3.0 的行業標准 JPA 應用程序的案例研究。通過一系列常見場景描述了遷移情況,並且在 每個場景中,對 Hibernate 和 OpenJPA 的實現進行了並排比較。並排比較為那些將現有 Hibernate/EJB 2.1 應用程序遷移到 OpenJPA/EJB 3.0,以及在下一個項目中使用 OpenJPA/EJB 3.0 的用戶提供了幫助 。

本文介紹了如何遷移組成遺留 Hibernate 應用程序的三個主要構建塊(應用程序源代碼、對象關系映 射和配置參數),並得出以下結論:

如果 Hibernate 應用程序源代碼構造良好,並封裝 Hibernate 調用,則對於常見場景,源代碼的遷 移非常簡單。對於沒有封裝 Hibernate 調用的程序,遷移將比較困難,但是與更改業務(會話、事務和 實體管理)邏輯相比,遷移還需要較多的語法更改。

由於對象關系映射已存在,所以您需要使用中間相遇遷移方法保持現有對象和數據模型。不能使用自 底向上方法從數據模型生成對象模型,也不能使用自頂向下方法從對象模型生成數據模型;遷移必須保持 兩個模型。本文手動實現了中間相遇映射,但是 Dali JPA 工具或 IBM Design Pattern 工具包(請參見 參考資料)可以自動進行 Hibernate XML 到 OpenJPA XML的大部分遷移。

將常見配置參數從 Hibernate 遷移到 OpenJPA 非常容易,但是還可以使用許多其他參數來優化 OpenJPA 可能需要(也可能不需要)的遺留 Hibernate 應用程序。因此,對優化不執行並排遷移。相反 ,先遷移常見配置參數,然後讓負載測試指示需要引入哪些 OpenJPA 優化參數。

本文沒有介紹 Hibernate 查詢到 OpenJPA 的遷移,因為對於 Hibernate 應用程序來說,具有滿足業 務需求的定義良好的域模型是比較普遍的。對於沒有定義良好的域模型和需要許多特殊查詢的應用程序, 則解決方案(如 iBatis)比對象關系映射工具(如 Hibernate 或 OpenJPA)更合適。也就是說,如果有 足夠的興趣,則後續文章可能將介紹 Hibernate 查詢到 OpenJPA 的遷移。

附錄:OpenJPA Java 5 注釋

注釋 A. 單個表繼承

//Participant (base) class
@Entity
@Table(name="T_PRTCPNT")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="PRTCPNT_CLASS")
public class Participant implements java.io.Serializable {
@Column(name="PRTCPNT_TID")
@Id private Long participantId;
...
}
// SalesRep subclass
@Entity
@DiscriminatorValue(value="SALES_REP")
public class SalesRep extends Participant {
...
}
// Administrator subclass
@Entity
@DiscriminatorValue(value="ADMIN")
public class Administrator extends Participant {
...
}

注釋 B. 連接繼承

// Participant (base) class
@Entity
@Table(name="T_PRTCPNT")
@Inheritance(strategy=InheritanceType.JOINED)
public class Participant implements Serializable {
@Column(name="PRTCPNT_TID")
@Id private Long participantId;
...
}
// SalesRep subclass
@Entity
@Table(name="T_SALESREP")
@PrimaryKeyJoinColumn(name="PRTCPNT_TID")
public SalesRep extends Participant {
...
}
// Administrator subclass
@Entity
@Table(name="T_ADMIN")
@PrimaryKeyJoinColumn(name="PRTCPNT_TID")
public Administrator extends Participant {
...
}

注釋 C. 可嵌入對象的一對一關系

// Employee (parent) class
@Entity
@Table(name="T_EMPLOYEE")
public class Employee implements Serializable {
@Embedded private EmployeeRecord employeeRec;
...
}
// EmployeeRecord (child) class
@Embeddable
public class EmployeeRecord implements Serializable {
...
}

注釋 D. 多對一關系

// Address (parent) class
@Entity
@Table(name="T_ADDRESS")
public class Address implements Serializable {
...
@Column(name="ADDR_TID")
@Id private Long addressId;
}
// Phone (child) class
@Entity
@Table(name="T_PHONE")
public class Phone implements Serializable {
...
@ManyToOne
@JoinColumn(name="ADDR_TID")
private Address address;
...
}

注釋 E. 一對多關系

// Address (parent) entity
@Entity
@Table(name="T_ADDRESS")
public class Address implements Serializable {
...
@OneToMany(mappedBy="address", cascade=CascadeType.ALL)
private Set<Phone> phones = new HashSet<Phone>();
...
@Column(name="ADDR_TID")
@Id private Long addressId;
}
// Phone (child) entity
@Entity
@Table(name="T_PHONE")
public class Phone implements Serializable {
...
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="ADDR_TID")
private Address address;
...
}

注釋 F. 多對多關系

// User (owner/child) entity
@Entity
@Table(name="T_USER")
public class User implements Serializable {
@Column(name="USER_TID")
@Id private Long userId;
@ManyToMany
@JoinTable(
name="T_USER_GROUP",
joinColumns=@JoinColumn(name="USER_TID"),
inverseJoinColumns=@JoinColumn(name="GROUP_TID"))
private Set<Group> groups = new HashSet<Group>();
...
}
// Group (non-owner/parent) entity
@Entity
@Table(name="T_GROUP")
public class Group implements Serializable {
@Column(name="GROUP_TID")
@Id private Long groupId;
@ManyToMany(mapppedBy="groups")
private Set<User> users = new HashSet<User>();
...
}

注釋 G. 延遲初始化

// Address (parent) entity
@Entity
@Table(name="T_ADDRESS")
public class Address implements Serializable {
...
@OneToMany(mappedBy="address", fetch=FetchType.EAGER)
private Set<Phone> phones = new HashSet<Phone>();;
...
@Column(name="ADDR_TID")
@Id private Long addressId;
}
// Phone (child) entity
@Entity
@Table(name="T_PHONE")
public class Phone implements Serializable {
...
@ManyToOne
@JoinColumn(name="ADDR_TID")
private Address address;
...
}

注釋 H. 對象標識

// Address entity
@Entity
@Table(name="T_ADDRESS")
public class Address implements Serializable {
...
@SequenceGenerator(name="AddressSeq", sequenceName="T_ADDRESS_SEQ")
@GeneratedValue(
strategy=GenerationType.SEQUENCE,
generator="AddressSeq")
@Column(name="ADDR_TID")
@Id private Long addressId;
...
}

注釋 I. 樂觀鎖定

@Entity
@Table(name="T_ADDRESS")
public class Address {
...
@Column(name="VERSION")
@Version private long version;
...
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved