對映

MappingR2dbcConverter 提供了豐富的對映支援。MappingR2dbcConverter 擁有豐富的元資料模型,可以將領域物件對映到資料行。對映元資料模型透過在領域物件上使用註解填充。然而,該基礎設施不僅限於將註解作為元資料資訊的唯一來源。MappingR2dbcConverter 還允許您透過遵循一組約定,在不提供任何額外元資料的情況下將物件對映到行。

本節描述了 MappingR2dbcConverter 的特性,包括如何使用約定將物件對映到行以及如何使用基於註解的對映元資料覆蓋這些約定。

在繼續閱讀本章之前,請先閱讀關於 物件對映基礎 的基本內容。

基於約定的對映

當未提供額外的對映元資料時,MappingR2dbcConverter 有一些將物件對映到行的約定。這些約定是

  • Java 類的短名稱以以下方式對映到表名。例如,com.bigbank.SavingsAccount 類對映到 SAVINGS_ACCOUNT 表名。同樣的名稱對映也適用於將欄位對映到列名。例如,firstName 欄位對映到 FIRST_NAME 列。您可以透過提供自定義的 NamingStrategy 來控制這種對映。更多詳情請參見對映配置。預設情況下,從屬性或類名派生的表名和列名在 SQL 語句中使用時沒有引號。您可以透過設定 RelationalMappingContext.setForceQuote(true) 來控制此行為。

  • 不支援巢狀物件。

  • 轉換器使用透過 CustomConversions 註冊的任何 Spring Converter 來覆蓋物件屬性到行中的列和值的預設對映。

  • 物件的欄位用於在行中的列之間進行轉換。不使用公共的 JavaBean 屬性。

  • 如果您有一個唯一的非零引數建構函式,並且其建構函式引數名稱與行的頂級列名匹配,則使用該建構函式。否則,使用零引數建構函式。如果存在多個非零引數建構函式,則丟擲異常。更多詳細資訊請參閱 物件建立

對映配置

預設情況下(除非明確配置),當您建立 DatabaseClient 時會建立一個 MappingR2dbcConverter 例項。您可以建立自己的 MappingR2dbcConverter 例項。透過建立自己的例項,您可以註冊 Spring converter 來將特定類對映到資料庫以及從資料庫映射出來。

您可以使用基於 Java 的元資料配置 MappingR2dbcConverterDatabaseClientConnectionFactory。以下示例使用了 Spring 的基於 Java 的配置

如果您將 R2dbcMappingContextsetForceQuote 設定為 true,則從類和屬性派生的表名和列名將使用資料庫特定的引號。這意味著可以在這些名稱中使用保留的 SQL 詞(例如 order)。您可以透過覆蓋 AbstractR2dbcConfigurationr2dbcMappingContext(Optional<NamingStrategy>) 來實現這一點。Spring Data 會將此類名稱的字母大小寫轉換為配置的資料庫在不使用引號時也使用的形式。因此,您可以在建立表時使用不帶引號的名稱,只要您不在名稱中使用關鍵字或特殊字元。對於符合 SQL 標準的資料庫,這意味著名稱會轉換為大寫。引號字元和名稱大小寫轉換的方式由使用的 Dialect 控制。有關如何配置自定義方言,請參閱R2DBC 驅動程式

用於配置 R2DBC 對映支援的 @Configuration 類
@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 方法向 converter 新增額外的 converter。

您可以透過將自定義的 NamingStrategy 註冊為 bean 來配置它。NamingStrategy 控制類和屬性的名稱如何轉換為表和列的名稱。

AbstractR2dbcConfiguration 會建立一個 DatabaseClient 例項,並以 databaseClient 為名稱將其註冊到容器中。

基於元資料的對映

為了充分利用 Spring Data R2DBC 支援中的物件對映功能,您應該使用 @Table 註解標註您的對映物件。雖然對映框架不一定需要此註解(即使沒有任何註解,您的 POJO 也能正確對映),但它能讓 classpath 掃描器找到並預處理您的領域物件以提取必要的元資料。如果您不使用此註解,您的應用程式在第一次儲存領域物件時會略微影響效能,因為對映框架需要構建其內部元資料模型,以便了解您領域物件的屬性以及如何持久化它們。以下示例展示了一個領域物件

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

@Table
public class Person {

  @Id
  private Long id;

  private Integer ssn;

  private String firstName;

  private String lastName;
}
@Id 註解告訴 mapper 您希望將哪個屬性用作主鍵。

預設型別對映

下表解釋了實體的屬性型別如何影響對映

源型別 目標型別 備註

基本型別和包裝型別

直通

可以使用顯式轉換器進行自定義。

JSR-310 日期/時間型別

直通

可以使用顯式轉換器進行自定義。

String, BigInteger, BigDecimal, 和 UUID

直通

可以使用顯式轉換器進行自定義。

列舉

String

可以透過註冊顯式轉換器進行自定義。

BlobClob

直通

可以使用顯式轉換器進行自定義。

byte[], ByteBuffer

直通

視為二進位制負載。

Collection<T>

T 型別的陣列

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

基本型別、包裝型別和 String 的陣列

包裝型別的陣列(例如 int[]Integer[]

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

驅動程式特定型別

直通

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

複雜物件

目標型別取決於已註冊的 Converter

需要顯式轉換器,否則不支援。

列的原生資料型別取決於 R2DBC 驅動程式的型別對映。驅動程式可以貢獻額外的簡單型別,例如 Geometry 型別。

對映註解概述

RelationalConverter 可以使用元資料來驅動物件到行的對映。可用的註解如下

  • @Id: 應用於欄位級別,標記主鍵。

  • @Table: 應用於類級別,表示此類是對映到資料庫的候選。您可以指定資料儲存在哪個表中。

  • @Transient: 預設情況下,所有欄位都對映到行。此註解會排除其應用的欄位儲存到資料庫中。瞬態屬性不能在持久化建構函式中使用,因為 converter 無法為建構函式引數提供值。

  • @PersistenceCreator: 標記一個給定的建構函式或靜態工廠方法(甚至包括包私有的)在從資料庫例項化物件時使用。建構函式引數按名稱對映到檢索到的行中的值。

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

  • @Column: 應用於欄位級別,描述列在行中的名稱,允許名稱與類的欄位名不同。使用 @Column 註解指定的名稱在 SQL 語句中使用時總是帶引號。對於大多數資料庫來說,這意味著這些名稱是區分大小寫的。這也意味著您可以在這些名稱中使用特殊字元。然而,不建議這樣做,因為它可能會導致與其他工具出現問題。

  • @Version: 應用於欄位級別,用於樂觀鎖,並在儲存操作時檢查修改。值為 null(對於基本型別是 zero)被視為實體為新的標記。初始儲存值為 zero(對於基本型別是 one)。每次更新時,版本會自動遞增。更多詳情請參閱樂觀鎖

對映元資料基礎設施定義在獨立的 spring-data-commons 專案中,該專案與技術無關。在 R2DBC 支援中使用特定子類來支援基於註解的元資料。也可以實施其他策略(如果需要的話)。

命名策略

按照約定,Spring Data 應用 NamingStrategy 來確定表名、列名和 schema 名,預設採用蛇形命名法 (snake case)。例如,一個名為 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 方法來配置 converter。本章開頭的示例展示瞭如何使用 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;
  }
}

請注意,converter 應用於單個屬性。集合屬性(例如 Collection<Person>)會被迭代並按元素進行轉換。不支援集合 converter(例如 Converter<List<Person>>, OutboundRow)。

R2DBC 使用包裝類基本型別(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 converter,其源型別和目標型別使用實際的列舉型別,以避免使用 Enum.name() 轉換。此外,您需要在驅動程式級別配置列舉型別,以便驅動程式知道如何表示列舉型別。

以下示例展示了原生讀取和寫入 Color 列舉值所涉及的元件

enum Color {
    Grey, Blue
}

class ColorConverter extends EnumWriteSupport<Color> {

}


class Product {
    @Id long id;
    Color color;

    // …
}