對映

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 標準的資料庫,這意味著名稱會轉換為大寫。引號字元和名稱大寫的方式由所使用的 R2dbcDialect 控制。有關如何配置自定義方言,請參閱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 方法向轉換器新增額外的轉換器。

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

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

基於元資料的對映

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

示例領域物件
@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 可以使用元資料來驅動物件到行的對映。以下註解可用

  • @Embedded:帶有此註解的屬性將對映到父實體的表,而不是單獨的表。允許指定生成的列是否應具有公共字首。如果由此實體生成的所有列都為 null,則註解的實體將為 null,即其所有屬性都將為 null,具體取決於 @Embedded.onEmpty() 的值。可以與 @Id 結合形成複合 id。

  • @Id:應用於欄位級別以標記主鍵。它可以與 @Embedded 結合形成複合 id。

  • @InsertOnlyProperty:將屬性標記為僅在插入期間寫入。聚合根上的此類屬性將只寫入一次,永不更新。請注意,在巢狀實體上,所有儲存操作都會導致插入,因此此註解對巢狀實體的屬性沒有影響。

  • @MappedCollection:允許配置集合或單個巢狀實體如何對映。idColumn 指定用於引用父實體主鍵的列。keyColumn 指定用於儲存 List 索引或 Map 鍵的列。

  • @Sequence:為註解的屬性指定一個數據庫序列以生成值。

  • @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 支援動態建立列名。一旦生成,名稱將被快取,因此它僅在每個對映上下文是動態的。

嵌入式 ID

識別符號屬性可以用 @Embedded 註解,允許使用複合 ID。完整的嵌入式實體被視為 ID,因此用於確定聚合是新聚合需要插入還是現有聚合需要更新的檢查是基於該實體,而不是其元素。大多數用例需要自定義 BeforeConvertCallback 來為新聚合設定 ID。

帶有複合 ID 的簡單實體
@Table("PERSON_WITH_COMPOSITE_ID")
record Person( (1)
    @Id @Embedded.Nullable Name pk, (2)
    String nickName,
    Integer age
) {
}

record Name(String first, String last) {
}
帶有複合 ID 的簡單實體的匹配表
CREATE TABLE PERSON_WITH_COMPOSITE_ID (
    FIRST VARCHAR(100),
    LAST VARCHAR(100),
    NICK_NAME VARCHAR(100),
    AGE INT,
    PRIMARY KEY (FIRST, LAST) (3)
);
1 實體可以以記錄形式表示,無需任何特殊考慮。
2 pk 被標記為 id 並嵌入
3 來自嵌入式 Name 實體的兩列構成了資料庫中的主鍵。

表建立的詳細資訊取決於所使用的資料庫。

只讀屬性

使用 @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 使用包裝基本型別(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;

    // …
}
© . This site is unofficial and not affiliated with VMware.