對應

MappingJdbcConverter 提供豐富的對應支援。MappingJdbcConverter 具有豐富的中繼資料模型,可將領域物件對應到資料列。對應中繼資料模型透過在您的領域物件上使用註解來填充。然而,基礎架構不限於僅使用註解作為中繼資料資訊的來源。MappingJdbcConverter 也讓您可以將物件對應到資料列,而無需提供任何額外的中繼資料,只需遵循一組慣例即可。

本節說明 MappingJdbcConverter 的功能,包括如何使用慣例將物件對應到資料列,以及如何使用基於註解的對應中繼資料覆寫這些慣例。

在繼續本章之前,請先閱讀關於 物件對應基礎 的基本知識。

基於慣例的對應

當未提供額外的對應中繼資料時,MappingJdbcConverter 具有一些將物件對應到資料列的慣例。這些慣例如下:

  • Java 類別的簡短名稱會以下列方式對應到資料表名稱。com.bigbank.SavingsAccount 類別對應到 SAVINGS_ACCOUNT 資料表名稱。相同的名稱對應也適用於將欄位對應到欄位名稱。例如,firstName 欄位對應到 FIRST_NAME 欄位。您可以透過提供自訂的 NamingStrategy 來控制此對應。請參閱 對應配置 以取得更多詳細資訊。從屬性或類別名稱衍生的資料表和欄位名稱預設會在 SQL 陳述式中不使用引號。您可以透過設定 RelationalMappingContext.setForceQuote(true) 來控制此行為。

  • 轉換器使用向 CustomConversions 註冊的任何 Spring Converter,以覆寫物件屬性到資料列欄位和值的預設對應。

  • 物件的欄位用於轉換為資料列中的欄位和從資料列中的欄位轉換而來。不會使用 public JavaBean 屬性。

  • 如果您有一個單一的非零引數建構子,其建構子引數名稱與資料列的最上層欄位名稱相符,則會使用該建構子。否則,會使用零引數建構子。如果有多個非零引數建構子,則會擲回例外。請參閱 物件建立 以取得更多詳細資訊。

您的實體中支援的類型

目前支援下列類型的屬性:

  • 所有原始類型及其包裝類型 (intfloatIntegerFloat 等等)

  • 列舉會對應到其名稱。

  • 字串

  • java.util.Datejava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTime

  • 如果您的資料庫支援,則上述類型的陣列和集合可以對應到陣列類型的欄位。

  • 您的資料庫驅動程式接受的任何內容。

  • 對其他實體的參考。它們被視為一對一關係,或嵌入式類型。對於一對一關係實體,是否具有 id 屬性是可選的。參考實體的資料表預期會有一個額外的欄位,其名稱基於參考實體,請參閱 反向參考。嵌入式實體不需要 id。如果存在,它會被對應為普通屬性,而沒有任何特殊意義。

  • Set<some entity> 被視為一對多關係。參考實體的資料表預期會有一個額外的欄位,其名稱基於參考實體,請參閱 反向參考

  • Map<simple type, some entity> 被視為限定的一對多關係。參考實體的資料表預期會有兩個額外的欄位:一個欄位名稱基於參考實體,用於外鍵 (請參閱 反向參考),另一個欄位名稱與外鍵相同,並附加 _key 後綴,用於對應鍵。

  • List<some entity> 對應為 Map<Integer, some entity>。預期會有相同的額外欄位,並且可以以相同的方式自訂使用的名稱。

對於 ListSetMap,反向參考的命名可以透過實作 NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)NamingStrategy.getKeyColumn(RelationalPersistentProperty property) 分別進行控制。或者,您可以使用 @MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name") 註解屬性。為 Set 指定鍵欄位無效。

對應註解總覽

RelationalConverter 可以使用中繼資料來驅動物件到資料列的對應。以下註解可用:

  • @Id:在欄位層級應用,以標記主鍵。

  • @Table:在類別層級應用,以指示此類別是資料庫對應的候選者。您可以指定資料庫儲存的資料表名稱。

  • @Transient:預設情況下,所有欄位都會對應到資料列。此註解會排除應用它的欄位儲存在資料庫中。暫時屬性不能在持久化建構子中使用,因為轉換器無法實現建構子引數的值。

  • @PersistenceCreator:標記給定的建構子或靜態工廠方法 (甚至是套件保護的方法),以便在從資料庫實例化物件時使用。建構子引數會依名稱對應到檢索到的資料列中的值。

  • @Value:此註解是 Spring Framework 的一部分。在對應框架中,它可以應用於建構子引數。這讓您可以使用 Spring Expression Language 陳述式來轉換在資料庫中檢索到的鍵值,然後再用於建構領域物件。為了參考給定資料列的欄位,必須使用如下表示式:@Value("#root.myProperty"),其中 root 指的是給定 Row 的根。

  • @Column:在欄位層級應用,以描述欄位在資料列中表示的名稱,讓名稱與類別的欄位名稱不同。使用 @Column 註解指定的名稱在 SQL 陳述式中使用時始終會加上引號。對於大多數資料庫,這表示這些名稱區分大小寫。這也表示您可以在這些名稱中使用特殊字元。但是,不建議這樣做,因為它可能會導致其他工具出現問題。

  • @Version:在欄位層級應用,用於樂觀鎖定,並在儲存操作時檢查修改。值為 null (原始類型為 zero) 被視為新實體的標記。最初儲存的值為 zero (原始類型為 one)。版本會在每次更新時自動遞增。

請參閱 樂觀鎖定 以取得更多參考資訊。

對應中繼資料基礎架構在單獨的 spring-data-commons 專案中定義,該專案與技術無關。JDBC 支援中使用特定的子類別來支援基於註解的中繼資料。也可以實施其他策略 (如果需要)。

參考實體

參考實體的處理是有限的。這是基於上述聚合根的概念。如果您參考另一個實體,則根據定義,該實體是您的聚合的一部分。因此,如果您移除參考,則先前參考的實體會被刪除。這也表示參考是一對一或一對多,而不是多對一或多對多。

如果您有多對一或多對多參考,則根據定義,您正在處理兩個不同的聚合。這些聚合之間的參考可以編碼為簡單的 id 值,這些值可以與 Spring Data JDBC 正確對應。更好的編碼方式是將它們設為 AggregateReference 的實例。AggregateReferenceid 值的包裝器,它將該值標記為對不同聚合的參考。此外,該聚合的類型會編碼在類型參數中。

反向參考

聚合中的所有參考都會導致資料庫中相反方向的外鍵關係。預設情況下,外鍵欄位的名稱是參考實體的資料表名稱。

或者,您可以選擇讓它們以參考實體的實體名稱命名,而忽略 @Table 註解。您可以透過在 RelationalMappingContext 上呼叫 setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING) 來啟用此行為。

對於 ListMap 參考,需要一個額外的欄位來保存清單索引或對應鍵。它基於外鍵欄位,並附加 _KEY 後綴。

如果您想要完全不同的方式來命名這些反向參考,您可以在 NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner) 中實作一種符合您需求的方式。

宣告和設定 AggregateReference
class Person {
	@Id long id;
	AggregateReference<Person, Long> bestFriend;
}

// ...

Person p1, p2 = // some initialization

p1.bestFriend = AggregateReference.to(p2.id);

您不應在實體中包含屬性來保存反向參考的實際值,也不應包含對應或清單的鍵欄位。如果您希望這些值在您的領域模型中可用,我們建議在 AfterConvertCallback 中執行此操作,並將值儲存在暫時值中。

  • 您已為其註冊合適的 類型。

命名策略

依照慣例,Spring Data 應用 NamingStrategy 來決定資料表、欄位和結構描述名稱,預設為 蛇形命名法。名為 firstName 的物件屬性會變成 first_name。您可以透過在應用程式上下文中提供 NamingStrategy 來調整它。

覆寫資料表名稱

當資料表命名策略與您的資料庫資料表名稱不符時,您可以使用 Table 註解來覆寫資料表名稱。此註解的元素 value 提供自訂的資料表名稱。以下範例將 MyEntity 類別對應到資料庫中的 CUSTOM_TABLE_NAME 資料表:

@Table("CUSTOM_TABLE_NAME")
class MyEntity {
    @Id
    Integer id;

    String name;
}

您可以使用 Spring Data 的 SpEL 支援 來動態建立資料表名稱。一旦產生,資料表名稱將會被快取,因此它僅在每個對應上下文中是動態的。

覆寫欄位名稱

當欄位命名策略與您的資料庫資料表名稱不符時,您可以使用 Column 註解來覆寫資料表名稱。此註解的元素 value 提供自訂的欄位名稱。以下範例將 MyEntity 類別的 name 屬性對應到資料庫中的 CUSTOM_COLUMN_NAME 欄位:

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

MappedCollection 註解可以用於參考類型 (一對一關係) 或 Set、List 和 Map (一對多關係)。註解的 idColumn 元素為參考另一個資料表中 id 欄位的外鍵欄位提供自訂名稱。在以下範例中,MySubEntity 類別的對應資料表具有 NAME 欄位,以及 MyEntity id 的 CUSTOM_MY_ENTITY_ID_COLUMN_NAME 欄位,用於關係原因:

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
    Set<MySubEntity> subEntities;
}

class MySubEntity {
    String name;
}

當使用 ListMap 時,您必須有一個額外的欄位來表示 List 中資料集的位置或 Map 中實體的鍵值。此額外欄位名稱可以使用 MappedCollection 註解的 keyColumn 元素來自訂。

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
    List<MySubEntity> name;
}

class MySubEntity {
    String name;
}

您可以使用 Spring Data 的 SpEL 支援 來動態建立欄位名稱。一旦產生,名稱將會被快取,因此它僅在每個對應上下文中是動態的。

嵌入式實體

嵌入式實體用於在您的 Java 資料模型中擁有值物件,即使您的資料庫中只有一個資料表。在以下範例中,您可以看到 MyEntity 使用 @Embedded 註解進行對應。這樣做的結果是,在資料庫中,預期會有一個包含兩個欄位 idname (來自 EmbeddedEntity 類別) 的 my_entity 資料表。

但是,如果結果集中 name 欄位實際上是 null,則整個 embeddedEntity 屬性會根據 @EmbeddedonEmpty 設定為 null,當所有巢狀屬性都為 null 時,onEmpty 會將物件設為 null
與此行為相反,USE_EMPTY 嘗試使用預設建構子或接受來自結果集的 nullable 參數值的建構子來建立新實例。

範例 1. 嵌入物件的範例程式碼
class MyEntity {

    @Id
    Integer id;

    @Embedded(onEmpty = USE_NULL) (1)
    EmbeddedEntity embeddedEntity;
}

class EmbeddedEntity {
    String name;
}
1 如果 namenull,則 Nulls embeddedEntity。使用 USE_EMPTY 來實例化 embeddedEntity,並為 name 屬性使用潛在的 null 值。

如果您在一個實體中多次需要一個值物件,可以使用 @Embedded 註解的可選 prefix 元素來實現。此元素表示前綴,並為嵌入式物件中的每個欄位名稱加上前綴。

使用快捷方式 @Embedded.Nullable@Embedded.Empty 分別代表 @Embedded(onEmpty = USE_NULL)@Embedded(onEmpty = USE_EMPTY),以減少冗長性並同時設定 JSR-305 @javax.annotation.Nonnull

class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1 @Embedded(onEmpty = USE_NULL) 的快捷方式。

包含 CollectionMap 的嵌入式實體將始終被視為非空,因為它們至少會包含空集合或對應。因此,即使使用 @Embedded(onEmpty = USE_NULL),此類實體也永遠不會為 null

唯讀屬性

使用 @ReadOnlyProperty 註解的屬性不會由 Spring Data 寫入資料庫,但在載入實體時會被讀取。

Spring Data 不會在寫入實體後自動重新載入實體。因此,如果您想要查看在資料庫中為此類欄位產生的資料,則必須明確地重新載入它。

如果註解的屬性是實體或實體集合,則它由單獨資料表中的一個或多個單獨資料列表示。Spring Data 不會對這些資料列執行任何插入、刪除或更新。

僅插入屬性

使用 @InsertOnlyProperty 註解的屬性僅會在插入操作期間由 Spring Data 寫入資料庫。對於更新,這些屬性將被忽略。

@InsertOnlyProperty 僅適用於聚合根。

自訂物件建構

對應子系統允許透過使用 @PersistenceConstructor 註解建構子來自訂物件建構。用於建構子參數的值會以下列方式解析:

  • 如果參數使用 @Value 註解,則會評估給定的表示式,並將結果用作參數值。

  • 如果 Java 類型具有一個屬性,其名稱與輸入資料列的給定欄位相符,則會使用其屬性資訊來選擇適當的建構子參數,以將輸入欄位值傳遞給該參數。這僅在參數名稱資訊存在於 Java .class 檔案中時才有效,您可以透過使用偵錯資訊編譯原始碼或在 Java 8 中使用 javac-parameters 命令列切換來實現這一點。

  • 否則,會擲回 MappingException,以指示無法繫結給定的建構子參數。

class OrderItem {

  private @Id final String id;
  private final int quantity;
  private final double unitPrice;

  OrderItem(String id, int quantity, double unitPrice) {
    this.id = id;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }

  // getters/setters omitted
}

使用明確轉換器覆寫對應

Spring Data 允許註冊自訂轉換器,以影響值在資料庫中的對應方式。目前,轉換器僅在屬性層級應用,也就是說,您只能將領域中的單個值轉換為資料庫中的單個值,反之亦然。不支援複雜物件和多個欄位之間的轉換。

使用已註冊的 Spring Converter 寫入屬性

以下範例顯示了 Converter 的實作,該實作將 Boolean 物件轉換為 String 值:

import org.springframework.core.convert.converter.Converter;

@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {

    @Override
    public String convert(Boolean source) {
        return source != null && source ? "T" : "F";
    }
}

這裡有幾件事需要注意:BooleanString 都是簡單類型,因此 Spring Data 需要提示此轉換器應在哪個方向應用 (讀取或寫入)。透過使用 @WritingConverter 註解此轉換器,您可以指示 Spring Data 將每個 Boolean 屬性作為 String 寫入資料庫。

使用 Spring Converter 讀取

以下範例顯示了 Converter 的實作,該實作將 String 轉換為 Boolean 值:

@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {

    @Override
    public Boolean convert(String source) {
        return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
    }
}

這裡有幾件事需要注意:StringBoolean 都是簡單類型,因此 Spring Data 需要提示此轉換器應在哪個方向應用 (讀取或寫入)。透過使用 @ReadingConverter 註解此轉換器,您可以指示 Spring Data 轉換資料庫中每個應指派給 Boolean 屬性的 String 值。

使用 JdbcConverter 註冊 Spring Converter

class MyJdbcConfiguration extends AbstractJdbcConfiguration {

    // …

    @Override
    protected List<?> userConverters() {
	return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
    }

}
在 Spring Data JDBC 的先前版本中,建議直接覆寫 AbstractJdbcConfiguration.jdbcCustomConversions()。這不再是必要的,甚至不建議這樣做,因為該方法會組裝用於所有資料庫的轉換、由使用的 Dialect 註冊的轉換以及使用者註冊的轉換。如果您從較舊版本的 Spring Data JDBC 遷移,並且覆寫了 AbstractJdbcConfiguration.jdbcCustomConversions(),則來自您的 Dialect 的轉換將不會被註冊。

如果您想要依賴 Spring Boot 來啟動 Spring Data JDBC,但仍然想要覆寫配置的某些方面,您可能想要公開該類型的 Bean。對於自訂轉換,您可以選擇註冊 JdbcCustomConversions 類型的 Bean,該 Bean 將被 Boot 基礎架構選取。若要了解更多相關資訊,請務必閱讀 Spring Boot 參考文件

JdbcValue

值轉換使用 JdbcValue 來豐富傳播到 JDBC 操作的值,並帶有 java.sql.Types 類型。如果您需要指定 JDBC 特定的類型而不是使用類型衍生,請註冊自訂寫入轉換器。此轉換器應將值轉換為 JdbcValue,後者具有值和實際的 JDBCType 的欄位。