跳轉到內容

Java 持久化/對映

來自華夏公益教科書

在 Java 中持久化某個東西,您需要做的第一件事是定義它如何持久化。這稱為對映過程 (詳細資訊)。多年來,對映過程出現了許多不同的解決方案,包括一些不需要您進行任何對映,而是允許您直接持久化任何東西的物件資料庫。物件關係對映工具會為資料模型生成物件模型,其中包含對映和持久化邏輯。ORM 產品提供了對映工具,允許將現有物件模型對映到現有資料模型,並將此對映元資料儲存在平面檔案、資料庫表、XML 和最終註釋中。

在 JPA 中,對映可以儲存在 Java 註釋中,也可以儲存在 XML 檔案中。JPA 的一個重要方面是,只需要最少的對映量。JPA 實現需要為對映物件的大多數方面提供預設值。

在 JPA 中對映物件的最低要求是定義哪些物件可以持久化。這可以透過用@Entity 註釋標記類,或在持久化單元的 ORM XML 檔案中為類新增<entity> 標籤來實現。此外,必須為類定義主鍵或唯一識別符號屬性。這可以透過用@Id 註釋標記類的一個欄位或屬性(get 方法),或在ORM XML 檔案中為類的屬性新增<id> 標籤來實現。

JPA 實現將預設所有其他對映資訊,包括預設表名、所有定義欄位或屬性的列名、關係的基數和對映、訪問物件的全部 SQL 和持久化邏輯。大多數 JPA 實現還提供在執行時生成資料庫表的選擇,因此開發人員只需很少的工作量就可以快速開發永續性 JPA 應用程式。

示例物件模型

[編輯 | 編輯原始碼]

示例資料模型

[編輯 | 編輯原始碼]

在註釋中持久化實體對映的示例

[編輯 | 編輯原始碼]
import javax.persistence.*;
...
@Entity
public class Employee {
    @Id
    private long id;
    private String firstName;
    private String lastName;
    private Address address;
    private List<Phone> phones;
    private Employee manager;
    private List<Employee> managedEmployees;
    ...
    ...
}

在 XML 中持久化實體對映的示例

[編輯 | 編輯原始碼]
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0"
        xmlns="http://java.sun.com/xml/ns/persistence/orm"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd">
    <description>The minimal mappings for a persistent entity in XML.</description>
    <entity name="Employee" class="org.acme.Employee" access="FIELD">
        <attributes>
            <id name="id"/>
        </attributes>
    </entity>
</entity-mappings>

訪問型別

[編輯 | 編輯原始碼]

JPA 允許將註釋放置在類欄位或屬性的get 方法上。這定義了類的訪問型別,即FIELDPROPERTY。所有註釋必須都放在欄位上,或者所有註釋都放在get 方法上,但不能兩者兼而有之(除非使用@Access 註釋)。JPA 沒有定義預設訪問型別(奇怪的是),因此預設值可能取決於 JPA 提供程式。預設值被認為是根據@Id 註釋的位置發生的,如果放在欄位上,則類使用FIELD 訪問,如果放在get 方法上,則類使用PROPERTY 訪問。訪問型別也可以透過 XML 在<entity> 元素中定義。可以使用@Access 註釋或access-type XML 屬性配置訪問型別。

對於FIELD 訪問,將直接訪問類欄位值以從資料庫中儲存和載入值。這通常透過反射或生成的位元組碼來完成,但這取決於 JPA 提供程式和配置。欄位可以是private 或任何其他訪問型別。FIELD 通常更安全,因為它避免了應用程式get/set 方法中可能發生的任何意外副作用程式碼。

對於PROPERTY 訪問,將使用類getset 方法從資料庫中儲存和載入值。這通常透過反射或生成的位元組碼來完成,但這取決於 JPA 提供程式和配置。PROPERTY 的優勢在於允許應用程式在將資料庫值儲存在物件中時執行轉換。使用者應該注意不要在get/set 方法中放置任何可能干擾持久化的副作用。

JPA 2.0 允許在特定欄位或get 方法上設定訪問型別。這允許類使用一個預設訪問機制,但對於一個屬性來說,可以使用不同的訪問型別。這可以用於需要透過一組資料庫特定getset 進行轉換的屬性,或者允許特定屬性避免其getset 方法中的副作用。這是透過在對映屬性上指定@Access 註釋或 XML 屬性來實現的。如果欄位/屬性的屬性名稱與對映屬性的屬性名稱不同,您可能還需要將其標記為@Transient

JPA 允許在persistence-unit-defaultsentity-mappings 中的實體預設值中設定持久化單元預設<access-type> 元素。

TopLink / EclipseLink : 預設使用FIELD 訪問。如果啟用了編織,並且使用欄位訪問,則使用生成的位元組碼直接訪問欄位。如果不使用編織,或使用屬性訪問,則使用反射。EclipseLink 還支援第三種訪問型別VIRTUAL,它可以從 ORM XML 中用於對映儲存在屬性 Map 中的動態屬性。

訪問型別示例

[編輯 | 編輯原始碼]
@Entity
@Access(AccessType.FIELD)
public class Employee {
    @Id
    private long id;
    private String firstName;
    private String lastName;
    @Transient
    private Money salary;
    @ManyToOne(fetch=FetchType.LAZY)
    private Employee manager;

    @Access(AccessType.PROPERTY)
    private BigDecimal getBDSalary() {
        return this.salary.toNumber();
    }
    private void setBDSalary(BigDecimal salary) {
        this.salary = new Money(salary);
    }

    private Money getSalary() {
        return this.salary;
    }
    private void setSalary(Money salary) {
        this.salary = salary;
    }
    ...
}

常見問題

[編輯 | 編輯原始碼]

我的註釋被忽略了

[編輯 | 編輯原始碼]
這通常發生在您對類的欄位和方法(屬性)都進行註釋時。您必須選擇欄位訪問或屬性訪問,並且保持一致。此外,在註釋屬性時,必須將註釋放在 get 方法上,而不是 set 方法上。還要確保您沒有在 XML 中定義相同的對映,因為這可能會覆蓋註釋。您也可能存在類路徑問題,例如類路徑上可能存在舊版本的類。

奇怪的行為

[編輯 | 編輯原始碼]
持久化出現奇怪行為有很多原因。一個可能導致奇怪行為的常見問題是在使用屬性訪問時,在 get 或 set 方法中放置副作用。因此,通常建議在對映中使用欄位訪問,即在變數上而不是在 get 方法上放置註釋。
例如,考慮
public void setPhones(List<Phone> phones) {
  for (Phone phone : phones) {
    phone.setOwner(this);
  }
  this.phones = phones;
}
這看起來可能是無害的,但這些副作用可能會產生意想不到的後果。例如,如果關係是lazy,這將具有在從資料庫中設定時始終例項化集合的效果。它還可能對某些 JPA 實現的持久化、合併和其他操作產生影響,從而導致重複插入、錯過更新或物件模型損壞。
我也見過一些明顯錯誤的屬性方法,例如總是返回新物件或副本的 get 方法,或者實際上沒有設定值的 set 方法。
一般來說,如果您要使用屬性訪問,請確保您的屬性方法沒有副作用。您甚至可以使用與應用程式不同的屬性方法。
華夏公益教科書