Mapping

MappingR2dbcConverter 提供丰富的映射支持。 MappingR2dbcConverter 具有丰富的元数据模型,可将域对象映射到数据行。使用域对象上的注释填充映射元数据模型。但是,该基础设施不限于使用注释作为元数据信息的唯一来源。 MappingR2dbcConverter 还允许您将对象映射到行,而无需提供任何其他元数据,方法是遵循一组约定。 本节介绍 MappingR2dbcConverter 的功能,包括如何使用约定将对象映射到行以及如何使用基于注释的映射元数据覆盖这些约定。 在继续本章之前,阅读有关 object-mapping.adoc的基础知识。

Convention-based Mapping

当未提供其他映射元数据时, MappingR2dbcConverter 有一些约定用于将对象映射到行。这些约定是:

  • 短 Java 类名以以下方式映射到表名。com.bigbank.SavingsAccount 类映射到 SAVINGS_ACCOUNT 表名。相同的名称映射用于将字段映射到列名称。例如,firstName 字段映射到 FIRST_NAME 列。您可以通过提供自定义 NamingStrategy 来控制此映射。有关更多详细信息,请参见 Mapping Configuration。从属性或类名派生的表名和列名在 SQL 语句中默认不带引号。您可以通过设置 RelationalMappingContext.setForceQuote(true) 来控制此行为。

  • 不支持内部对象。

  • 该转换器使用已使用 CustomConversions 注册的任何 Spring 转换器来覆盖对象属性到行列和值的默认映射。

  • 对象的字段用于转换到和从行中的列。公共 JavaBean 属性未使用。

  • 如果你只有一个非零参数构造函数,且其构造函数参数名称匹配行的顶级列名称,则使用该构造函数。否则,将使用零参数构造函数。如果有多于一个非零参数构造函数,则会引发异常。请参阅 Object Creation 了解更多详细信息。

Mapping Configuration

默认情况下(除非明确配置),当您创建 DatabaseClient 时,将创建 MappingR2dbcConverter 的实例。您可以创建自己的 MappingR2dbcConverter 实例。通过创建自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库并从数据库映射。

您可以使用基于 Java 的元数据配置 MappingR2dbcConverter 以及 DatabaseClientConnectionFactory 。以下示例使用基于 Spring 的 Java 配置:

如果您将 R2dbcMappingContext tosetForceQuote 设置为 true,则从类和属性派生的表和列名将与特定数据库的引号一起使用。这意味着在这些名称中可以使用保留的 SQL 单词(例如 order)。您可以通过覆盖 AbstractR2dbcConfigurationr2dbcMappingContext(Optional<NamingStrategy>) 来实现。Spring Data 将此类名称的字母大小写转换为在不使用引号时配置的数据库也使用的形式。因此,只要您的名称中不使用关键字或特殊字符,您就可以在创建表时使用不带引号的名称。对于遵守 SQL 标准的数据库,这意味着名称将转换为大写。引号字符和名称的大写方式由所用的 Dialect 控制。有关如何配置自定义方言,请参见 R2DBC Drivers

@Configuration class to configure R2DBC mapping support
@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 的名称将它注册到容器。

Metadata-based Mapping

要充分利用 Spring Data R2DBC 支持中的对象映射功能,您应该使用 @Table 注释为您的映射对象进行注释。虽然映射框架不需要此注释(即使没有任何注释,您的 POJO 也映射正确),但它可以让类路径扫描器查找和预处理您的域对象以提取必要的元数据。如果您不使用此注释,则在您首次存储域对象时,您的应用程序会略微受到性能影响,因为映射框架需要构建其内部元数据模型,以便它知道您的域对象的属性以及如何持久它们。以下示例显示了一个域对象:

Example domain object
package com.mycompany.domain;

@Table
public class Person {

  @Id
  private Long id;

  private Integer ssn;

  private String firstName;

  private String lastName;
}

@Id 注释告诉映射器您希望使用哪个属性作为主键。

Default Type Mapping

下表解释了实体的属性类型如何影响映射:

Source Type Target Type Remarks

原始类型和封装类型

Passthru

可以使用 Explicit Converters 进行定制。

JSR-310 Date/Time types

Passthru

可以使用 Explicit Converters 进行定制。

StringBigIntegerBigDecimalUUID

Passthru

可以使用 Explicit Converters 进行定制。

Enum

String

可以通过注册 Explicit Converters 进行定制。

Blob and Clob

Passthru

可以使用 Explicit Converters 进行定制。

byte[], ByteBuffer

Passthru

Considered a binary payload.

Collection<T>

Array of T

如果已配置 driver支持,则转换至数组类型,否则不支持。

基本类型数组、封装类型和 String

包装器类型的数组(例如 int[]Integer[])

如果已配置 driver支持,则转换至数组类型,否则不支持。

Driver-specific types

Passthru

受使用的 R2dbcDialect 作为一个简单类型所提供。

Complex objects

目标类型取决于已注册的 Converter

需要 Explicit Converters,否则不受支持。

列的本机数据类型取决于 R2DBC 驱动程序类型映射。驱动程序可以提供其他简单类型,例如 Geometry 类型。

Mapping Annotation Overview

`RelationalConverter`可以使用元数据来驱动对象到行的映射。下列注解是可用的:

  • @Id:应用于字段级别以标记主键。

  • @Table:应用于类级别以指示 此类 是映射到数据库的候选者。您可以指定存储数据库的表格的名称。

  • @Transient:默认情况下,所有字段都映射到行。这个批注将应用其排除字段从存储在数据库中。瞬态属性不能在持久化构造函数中使用,因为转换器无法使构造函数实参的值具体化。

  • @PersistenceCreator:标记一个给定的构造函数,甚至是包保护的,或者静态工厂方法 — 使用的时候从数据库实例化对象。构造函数实参通过名称映射到检索行中的值。

  • @Value:这个批注是 Spring 框架的一部分。在映射框架中它可以应用到构造函数实参。这使您能够使用 Spring 表达式语言语句来转换在数据库中检索的密钥值,然后才能用于构造域对象。为了引用给定行的列,必须使用以下表达式:@Value("#root.myProperty"),其中 root 指向给定 Row 的根。

  • @Column:应用于字段层级,用于描述列的名称,使其在行中显示为指定名称,允许该名称不同于类的字段名称。使用 @Column 注释指定的名称在 SQL 语句中使用时,始终会被引用。对于大多数数据库,这意味着这些名称区分大小写。这意味着你可以在这些名称中使用特殊字符。然而,不建议这样做,因为它可能会给其他工具带来麻烦。

  • @Version:应用于字段层级,用于乐观锁且在保存操作中进行修改检查。值是 null (对于原始类型为 zero),被视为新实体的标记。初始存储的值是 zero (对于原始类型为 one)。版本在每次更新时都会自动增加。

映射元数据基础架构在独立的 spring-data-commons 项目中定义,该项目与技术无关。R2DBC 支持中使用了特定的子类以支持基于注释的元数据。还可以制定其他策略(如果有需求)。

Naming Strategy

Naming Strategy

根据习惯,Spring Data 应用了一个 NamingStrategy,以确定表、列和模式名称,其默认为 snake case。名为 firstName`的对象属性会成为 `first_name。您可以通过在应用程序上下文中提供 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/NamingStrategy.html[NamingStrategy] 来调整它。

Override table names

当表命名策略不匹配您的数据库表名称时,您可以使用 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Table.html[@Table] 注解覆盖表名称。此注解的 `value`元素提供了自定义表名称。以下示例将 `MyEntity`类映射到数据库中的 `CUSTOM_TABLE_NAME`表:

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

    String name;
}

Override column names

当列命名策略不匹配您的数据库表名称时,您可以使用 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Column.html[@Column] 注解覆盖表名称。此注解的 `value`元素提供了自定义列名称。以下示例将 `MyEntity`类的 `name`属性映射到数据库中的 `CUSTOM_COLUMN_NAME`列:

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

Read Only Properties

使用 @ReadOnlyProperty 注解的属性不会被 Spring Data 写入到数据库中,但当加载实体时,这些属性将会被读取。

Spring Data 不会自动在写入实体后重新加载此实体。因此,如果你想要查看在数据库中为这些列生成的数据,那么就必须显式地重新加载此实体。

如果带注解的属性是实体或实体集合,则它将由各单独表中的一个或者若干单独行来表示。Spring Data 不会针对这些行执行任何插入、删除或者更新。

Insert Only Properties

使用 @InsertOnlyProperty 注解的属性仅在插入操作期间由 Spring Data 写入到数据库中。对于更新,将忽略这些属性。

@InsertOnlyProperty 仅受支持于聚合根。

Customized Object Construction

映射子系统允许通过使用 @PersistenceConstructor 注解注解构造函数来自定义对象构造。用于构造函数参数的值将按照以下方式解析:

  • 如果用 @Value 批注标注一个参数,则求值给定的表达式,结果用作参数值。

  • 如果 Java 类型具有与其输入行给定字段匹配的名称的属性,则其属性信息用于选择将输入字段值传递到的合适的构造函数参数。这仅在 Java 8 中的 Java .class 文件中存在参数名称信息时有效,您可以通过编译带有调试信息的源或使用 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
}

Overriding Mapping with Explicit Converters

在存储和查询对象时,通常方便地使用 R2dbcConverter 实例来处理所有 Java 类型到 OutboundRow 实例的映射。但是,您有时可能希望 R2dbcConverter 实例完成大部分工作,但让您有选择地处理特定类型的转换——也许是为了优化性能。

要自己有选择地处理转换,请使用 org.springframework.core.convert.converter.Converter 实例向 R2dbcConverter 注册一个或多个实例。

您可以在 AbstractR2dbcConfiguration 中使用 r2dbcCustomConversions 方法配置转换器。示例 at the beginning of this chapter 展示了如何使用 Java 执行配置。

自定义顶级实体转换需要不对称类型进行转换。入站数据从 R2DBC 的 Row 提取。出站数据(用于 INSERT/UPDATE 语句)表示为 OutboundRow,然后组装到语句中。

以下 Spring 转换器实现的示例将 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;
  }
}

Overriding Enum Mapping with Explicit Converters

某些数据库,例如 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;

    // …
}