對應

MappingR2dbcConverter 提供豐富的對應支援。MappingR2dbcConverter 具有豐富的元數據模型,允許將領域物件對應到資料列。對應元數據模型透過使用領域物件上的註解來填充。然而,基礎架構不限於僅使用註解作為元數據資訊的唯一來源。MappingR2dbcConverter 也允許您透過遵循一組慣例,在不提供任何額外元數據的情況下將物件對應到列。

本節描述 MappingR2dbcConverter 的功能,包括如何使用慣例將物件對應到列,以及如何使用基於註解的對應元數據來覆寫這些慣例。

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

基於慣例的對應

當未提供額外的對應元數據時,MappingR2dbcConverter 具有一些將物件對應到列的慣例。這些慣例是

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

  • 不支援巢狀物件。

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

  • 物件的欄位用於轉換為列中的欄位和從列中的欄位轉換。不使用公開的 JavaBean 屬性。

  • 如果您有一個單一的非零引數建構子,其建構子引數名稱與列的頂層欄位名稱匹配,則使用該建構子。否則,使用零引數建構子。如果有多個非零引數建構子,則會拋出例外。有關更多詳細資訊,請參閱 物件建立

對應配置

預設情況下(除非明確配置),當您建立 DatabaseClient 時,會建立 MappingR2dbcConverter 的實例。您可以建立自己的 MappingR2dbcConverter 實例。透過建立您自己的實例,您可以註冊 Spring 轉換器,以將特定類別對應到資料庫和從資料庫對應。

您可以使用基於 Java 的元數據來配置 MappingR2dbcConverter 以及 DatabaseClientConnectionFactory。以下範例使用 Spring 的基於 Java 的配置

如果您將 R2dbcMappingContextsetForceQuote 設定為 true,則從類別和屬性衍生的表格和欄位名稱將與資料庫特定的引號一起使用。這表示在這些名稱中使用保留的 SQL 字詞(例如 order)是可以的。您可以透過覆寫 AbstractR2dbcConfigurationr2dbcMappingContext(Optional<NamingStrategy>) 來執行此操作。當不使用引號時,Spring Data 會將此類名稱的字母大小寫轉換為配置的資料庫也使用的形式。因此,當您建立表格時,可以使用不帶引號的名稱,只要您在名稱中不使用關鍵字或特殊字元即可。對於遵循 SQL 標準的資料庫,這表示名稱將轉換為大寫。引號字元和名稱大寫的方式由使用的 Dialect 控制。有關如何配置自訂方言,請參閱 R2DBC 驅動程式

@Configuration 類別,用於配置 R2DBC 對應支援
@Configuration
public class MyAppConfig extends AbstractR2dbcConfiguration {

  public ConnectionFactory connectionFactory() {
    return ConnectionFactories.get("r2dbc:…");
  }

  // the following are optional

  @Override
  protected List<Object> getCustomConverters() {
    return List.of(new PersonReadConverter(), new PersonWriteConverter());
  }
}

AbstractR2dbcConfiguration 要求您實作一個定義 ConnectionFactory 的方法。

您可以透過覆寫 r2dbcCustomConversions 方法,將其他轉換器新增至轉換器。

您可以透過將自訂的 NamingStrategy 註冊為 bean 來配置它。 NamingStrategy 控制類別和屬性的名稱如何轉換為表格和欄位的名稱。

AbstractR2dbcConfiguration 建立 DatabaseClient 實例,並以 databaseClient 的名稱將其註冊到容器中。

基於元數據的對應

為了充分利用 Spring Data R2DBC 支援內部的物件對應功能,您應該使用 @Table 註解來註解您對應的物件。雖然對應框架不需要此註解(即使沒有任何註解,您的 POJO 也能正確對應),但它允許類別路徑掃描器尋找並預先處理您的領域物件以提取必要的元數據。如果您不使用此註解,您的應用程式在第一次儲存領域物件時會受到輕微的效能影響,因為對應框架需要建立其內部元數據模型,以便它了解您的領域物件的屬性以及如何持久化它們。以下範例顯示了一個領域物件

領域物件範例
package com.mycompany.domain;

@Table
public class Person {

  @Id
  private Long id;

  private Integer ssn;

  private String firstName;

  private String lastName;
}
@Id 註解告訴對應器您想要使用哪個屬性作為主鍵。

預設類型對應

下表說明實體的屬性類型如何影響對應

來源類型 目標類型 備註

原始類型和包裝器類型

直接傳遞

可以使用 明確轉換器 自訂。

JSR-310 日期/時間類型

直接傳遞

可以使用 明確轉換器 自訂。

StringBigIntegerBigDecimalUUID

直接傳遞

可以使用 明確轉換器 自訂。

列舉

字串

可以透過註冊 明確轉換器 來自訂。

BlobClob

直接傳遞

可以使用 明確轉換器 自訂。

byte[]ByteBuffer

直接傳遞

視為二進位酬載。

Collection<T>

T 陣列

如果配置的 驅動程式 支援,則轉換為陣列類型,否則不支援。

原始類型、包裝器類型和 String 的陣列

包裝器類型陣列(例如 int[]Integer[]

如果配置的 驅動程式 支援,則轉換為陣列類型,否則不支援。

驅動程式特定的類型

直接傳遞

由使用的 R2dbcDialect 作為簡單類型貢獻。

複雜物件

目標類型取決於註冊的 Converter

需要 明確轉換器,否則不支援。

欄位的原生資料類型取決於 R2DBC 驅動程式類型對應。驅動程式可以貢獻其他簡單類型,例如幾何類型。

對應註解總覽

RelationalConverter 可以使用元數據來驅動物件到列的對應。以下註解可用

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

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

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

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

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

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

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

對應元數據基礎架構在單獨的 spring-data-commons 專案中定義,該專案與技術無關。特定的子類別在 R2DBC 支援中使用,以支援基於註解的元數據。如果需要,也可以採用其他策略。

命名策略

依照慣例,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;
}

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

唯讀屬性

使用 @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
}

使用明確轉換器覆寫對應

在儲存和查詢物件時,通常方便擁有 R2dbcConverter 實例來處理所有 Java 類型到 OutboundRow 實例的對應。但是,您有時可能希望 R2dbcConverter 實例執行大部分工作,但讓您選擇性地處理特定類型的轉換,或許是為了最佳化效能。

若要選擇性地自行處理轉換,請向 R2dbcConverter 註冊一個或多個 org.springframework.core.convert.converter.Converter 實例。

您可以使用 AbstractR2dbcConfiguration 中的 r2dbcCustomConversions 方法來配置轉換器。本章開頭的 範例 顯示如何使用 Java 執行配置。

自訂頂層實體轉換需要不對稱類型進行轉換。輸入資料從 R2DBC 的 Row 中提取。輸出資料(用於 INSERT/UPDATE 陳述式)表示為 OutboundRow,然後組裝成陳述式。

以下 Spring Converter 實作範例示範如何從 Row 轉換為 Person POJO

@ReadingConverter
 public class PersonReadConverter implements Converter<Row, Person> {

  public Person convert(Row source) {
    Person p = new Person(source.get("id", String.class),source.get("name", String.class));
    p.setAge(source.get("age", Integer.class));
    return p;
  }
}

請注意,轉換器應用於單數屬性。集合屬性(例如 Collection<Person>)會逐個元素迭代和轉換。不支援集合轉換器(例如 Converter<List<Person>>, OutboundRow)。

R2DBC 使用 boxed 原始類型(Integer.class 而不是 int.class)來傳回原始值。

以下範例示範如何從 Person 轉換為 OutboundRow

@WritingConverter
public class PersonWriteConverter implements Converter<Person, OutboundRow> {

  public OutboundRow convert(Person source) {
    OutboundRow row = new OutboundRow();
    row.put("id", Parameter.from(source.getId()));
    row.put("name", Parameter.from(source.getFirstName()));
    row.put("age", Parameter.from(source.getAge()));
    return row;
  }
}

使用明確轉換器覆寫列舉對應

某些資料庫(例如 Postgres)可以使用其資料庫特定的列舉欄位類型以原生方式寫入列舉值。Spring Data 預設會將 Enum 值轉換為 String 值,以獲得最大的可攜性。若要保留實際的列舉值,請註冊一個 @Writing 轉換器,其來源和目標類型使用實際的列舉類型,以避免使用 Enum.name() 轉換。此外,您需要在驅動程式層級配置列舉類型,以便驅動程式知道如何表示列舉類型。

以下範例顯示以原生方式讀取和寫入 Color 列舉值所涉及的元件

enum Color {
    Grey, Blue
}

class ColorConverter extends EnumWriteSupport<Color> {

}


class Product {
    @Id long id;
    Color color;

    // …
}