跳到內容

Java 永續性/表

來自華夏公益教科書,開放的書籍,開放的世界

一個是關係型資料庫的基本持久結構。表包含一個列列表,該列表定義表的結構,以及一個行列表,該列表定義表的資料。每個列都有一個特定的型別,通常還有大小。標準的關係型型別集限於基本型別,包括數字、字元、日期時間和二進位制(儘管大多數現代資料庫都有其他型別和型別系統)。表也可以具有約束,這些約束定義限制行資料的規則,例如主鍵、外部索引鍵和唯一約束。表還具有其他工件,例如索引、分割槽和觸發器。

永續性類的典型對映將該類對映到單個表。在 JPA 中,這透過@Table註釋或<table>XML 元素來定義。如果沒有表註釋,JPA 實現將為該類自動分配一個表。JPA 預設表名是類名(不包括包)首字母大寫。類的每個屬性都將儲存在表中的一個列中。

具有單個表的實體的示例對映註釋

[編輯 | 編輯原始碼]
...
@Entity
@Table(name="EMPLOYEE")
public class Employee {
    ...
}

具有單個表的實體的示例對映 XML

[編輯 | 編輯原始碼]
<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <table name="EMPLOYEE"/>
</entity>

儘管在理想情況下每個類都應該對映到一個表,但這並不總是可行的。其他情況包括

  • 多表 : 一個類對映到 2 個或多個表。
  • 共享表 : 2 個或多個類儲存在同一個表中。
  • 繼承 : 一個類參與繼承,並具有繼承表和本地表。
  • 檢視 : 一個類對映到一個檢視。
  • 儲存過程 : 一個類對映到一組儲存過程。
  • 分割槽 : 一個類的某些例項對映到一個表,其他例項對映到另一個表。
  • 複製 : 一個類的資料被複制到多個表。
  • 歷史 : 一個類具有歷史資料。

這些都是高階情況,其中一些由 JPA 規範處理,而許多則沒有。以下部分將進一步探討每種情況,包括 JPA 規範支援的內容,在規範內解決問題的方法,以及如何使用一些 JPA 實現擴充套件來處理這種情況。

有時一個類會對映到多個表。這通常發生在遺留資料模型或現有資料模型中,其中物件模型和資料模型不匹配。它也可能發生在子類資料儲存在其他表中的繼承中。多表也可能出於效能、分割槽或安全原因使用。

JPA 允許將多個表分配給單個類。可以使用@SecondaryTable和 SecondaryTables 註釋或<secondary-table>元素。預設情況下,假定@Id列在兩個表中,這樣次要表的@Id列就是次要表的主鍵,也是對第一個表的外部鍵。如果第一個表的@Id列沒有相同的名稱,可以使用@PrimaryKeyJoinColumn<primary-key-join-column>來定義外部鍵連線條件。

在多表實體中,每個對映必須定義對映的列來自哪個表。這是使用@Column@JoinColumn註釋或 XML 元素的table屬性來完成的。預設情況下,使用類的主表,因此您只需要為次要表設定表。對於繼承,預設表是正在對映的子類的主表。

具有多表的實體的示例對映註釋

[編輯 | 編輯原始碼]
...
@Entity
@Table(name="EMPLOYEE")
@SecondaryTable(name="EMP_DATA",
                pkJoinColumns = @PrimaryKeyJoinColumn(name="EMP_ID", referencedColumnName="ID")
               )
public class Employee {
    ...
    @Column(name="YEAR_OF_SERV", table="EMP_DATA")
    private int yearsOfService;

    @OneToOne
    @JoinColumn(name="MGR_ID", table="EMP_DATA", referencedColumnName="ID")
    private Employee manager;
    ...
}

具有多表的實體的示例對映 XML

[編輯 | 編輯原始碼]
<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <table name="EMPLOYEE"/>
    <secondary-table name="EMP_DATA">
        <primary-key-join-column name="EMP_ID" referenced-column-name="ID"/>
    </secondary-table>
    <attributes>
        ...
        <basic name="yearsOfService">
            <column name="YEAR_OF_SERV" table="EMP_DATA"/>
        </basic>
        <one-to-one name="manager">
            <join-column name="MGR_ID" table="EMP_DATA" referenced-column-name="ID"/>
        </one-to-one>
    </attributes>
</entity>

使用@PrimaryKeyJoinColumn,名稱指的是次要表中的外部索引鍵列,而 referencedColumnName 指的是第一個表中的主鍵列。如果您有多個次要表,它們必須始終引用第一個表。在定義表的模式時,通常將次要表中的連線列定義為該表的主鍵,以及對第一個表的外部鍵。根據您定義的外部鍵約束,表的順序可能很重要,該順序通常與 JPA 實現插入表順序匹配,因此請確保表順序與約束依賴項匹配。

對於具有多個表的類的關係,外部索引鍵(連線列)始終對映到目標的主表。JPA 不允許外部索引鍵對映到目標物件主表以外的表。通常情況下,這不是問題,因為外部索引鍵幾乎總是對映到主表的 id/主鍵,但在一些高階場景中,這可能是一個問題。一些 JPA 產品允許列或連線列使用列的限定名稱(例如,@JoinColumn(referenceColumnName="EMP_DATA.EMP_NUM")),以允許這種型別的關係。一些 JPA 產品也可能透過自己的 API、註釋或 XML 支援這一點。

具有外部索引鍵的多個表

[編輯 | 編輯原始碼]

有時,您可能有一個輔助表,它是透過從主表到輔助表的,而不是從輔助表到主表的外部索引鍵來引用的。您甚至可能在兩個輔助表之間有一個外部索引鍵。假設您有一個EMPLOYEE表和一個ADDRESS表,EMPLOYEE透過ADDRESS_ID外部索引鍵引用ADDRESS,並且(出於某種奇怪的原因),您只想使用單個 Employee 類來儲存來自兩個表的 data。JPA 規範沒有直接涵蓋這一點,因此,如果遇到這種情況,首先要考慮的是,如果您有靈活性,請更改資料模型以符合規範。您還可以更改物件模型,為每個表定義一個類,在本例中為 Employee 類和 Address 類,這通常是最好的解決方案。您還應該檢查您的 JPA 實現以檢視它在該區域支援哪些擴充套件。

解決問題的一種方法是簡單地交換您的主表和輔助表。這將導致輔助表引用主表的主鍵,並且在規範範圍內。但是,這將產生副作用,其中之一是您現在將物件的 primary key 從EMP_ID更改為ADDRESS_ID,並且可能還會有其他對映和查詢影響。如果您有超過 2 個表,這可能也不起作用。

另一種選擇是隻在@PrimaryKeyJoinColumn中使用外部索引鍵列,這在技術上將是倒退的,並且可能不受規範支援,但對某些 JPA 實現可能有效。但是,這會導致表插入順序與外部索引鍵約束不匹配,因此需要刪除約束或延遲約束。

還可以透過資料庫檢視來對映場景。可以定義一個檢視來連線兩個表,並且類可以對映到檢視而不是表。檢視在某些資料庫上是隻讀的,但許多資料庫也允許寫入,或者允許使用觸發器來處理寫入操作。

一些 JPA 實現提供擴充套件來處理這些場景。

TopLinkEclipseLink : 為其對映模型ClassDescriptor.addForeignKeyFieldNameForMultipleTable()提供專有 API,該 API 允許在輔助表之間定義任意複雜的外部索引鍵關係。這可以透過使用@DescriptorCustomizer註釋和DescriptorCustomizer類來配置。

多表連線

[編輯 | 編輯原始碼]

有時,資料模型和物件模型根本無法很好地協作。資料庫可能是遺留模型,與新的應用程式模型不匹配,或者 DBA 或物件架構師可能有點瘋狂。在這些情況下,您可能需要高階的多表連線。

這些示例包括有兩個表,它們不是透過其主鍵或外部索引鍵,而是透過一些常量或計算來關聯的。假設您有一個EMPLOYEE表和一個ADDRESS表,ADDRESS表有一個EMP_ID外部索引鍵到EMPLOYEE表,但每個員工都有多個地址,並且只希望TYPE"HOME"的地址。在這種情況下,希望將來自兩個表的資料對映到Employee物件中。需要一個連線表示式,其中外部索引鍵匹配並且常量匹配。

同樣,這種情況可以透過重新設計資料或物件模型,或者透過使用檢視來處理。一些 JPA 實現提供擴充套件來處理這些場景。

TopLinkEclipseLink : 為其對映模型DescriptorQueryManager.setMultipleTableJoinExpression()提供專有 API,該 API 允許定義任意複雜的多表連線。這可以透過使用@DescriptorCustomizer註釋和DescriptorCustomizer類來配置。

多表外連線

[編輯 | 編輯原始碼]

多表對映的另一個變態是希望對輔助表進行外連線。如果輔助表可能或可能沒有為物件定義行,則可能需要這樣做。通常,如果要嘗試這樣做,則物件應該是隻讀的,因為寫入可能或可能不存在的行可能很棘手。

JPA 不直接支援這一點,如果遇到這種情況,最好重新考慮資料模型或物件模型設計。同樣,可以透過資料庫檢視來對映這一點,其中使用外連線在檢視中連線表。

一些 JPA 實現支援為多個表使用外連線。

Hibernate : 這可以透過使用 Hibernate@Table註釋並將其optional屬性設定為true來實現。這將配置 Hibernate 使用外連線來讀取表,並且如果對映到該表的所有屬性都為 null,則不會寫入該表。
TopLinkEclipseLink : 如果資料庫支援在 where 子句中使用外連線語法(Oracle、Sybase、SQL Server),則可以使用多表連線表示式來配置要用於讀取表的外連線。

包含特殊字元和混合大小寫字母的表

[編輯 | 編輯原始碼]

一些 JPA 提供商可能在包含特殊字元(如空格)的表名和列名方面存在問題。通常,最好使用標準字元、不使用空格,並且所有名稱都使用大寫字母。只要資料庫和 JDBC 驅動程式支援字元集,國際語言應該沒問題。

可能需要用引號括住包含特殊字元或在某些情況下包含混合大小寫字母的表名和列名。例如,如果表名包含空格,則可以按以下方式定義:

  @Table("\"Employee Data\"")

一些資料庫支援混合大小寫字母的表名和列名,而其他資料庫則區分大小寫。如果您的資料庫不區分大小寫,或者您希望您的資料模型可移植,則最好使用全大寫字母的名稱。這在 JPA 中通常不是什麼大問題,因為您很少直接從應用程式中使用表名和列名,但在某些情況下(如果使用本機 SQL 查詢)可能會出現問題。

表限定符、模式或建立者

[編輯 | 編輯原始碼]

資料庫表可能需要以表限定符為字首,例如表的建立者,或其名稱空間、模式或目錄。一些資料庫還支援將錶鏈接到其他資料庫,因此連結名稱也可以是表限定符。

在 JPA 中,可以透過schemacatalog屬性在表上設定表限定符。通常,使用哪個屬性並不重要,因為兩者都只是在表名前加上字首。從技術上講,您甚至可以將完整的名稱 "schema.table" 作為表的名稱,並且它將起作用。在 schema 或 catalog 中設定字首的好處是可以為整個永續性單元設定預設表限定符,此外,不設定真實表名可能會影響本機 SQL 查詢。

如果所有表都需要相同的表限定符,您可以在 orm.xml 中設定預設值。

具有限定表的實體的示例對映註釋

[編輯 | 編輯原始碼]
...
@Entity
@Table(name="EMPLOYEE", schema="ACME")
public class Employee {
    ...
}

預設(整個永續性單元)表限定符的示例對映 XML

[編輯 | 編輯原始碼]
<entity-mappings>
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <schema name="ACME"/>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
    ....
</entity-mappings>

預設(orm 檔案)表限定符的示例對映 XML

[編輯 | 編輯原始碼]
<entity-mappings>
    <schema name="ACME"/>
    ...
</entity-mappings>
華夏公益教科書