Mapping

MappingR2dbcConverter 提供丰富的映射支持。 MappingR2dbcConverter 具有丰富的元数据模型,可将域对象映射到数据行。使用域对象上的注释填充映射元数据模型。但是,该基础设施不限于使用注释作为元数据信息的唯一来源。 MappingR2dbcConverter 还允许您将对象映射到行,而无需提供任何其他元数据,方法是遵循一组约定。

Rich mapping support is provided by the MappingR2dbcConverter. MappingR2dbcConverter has a rich metadata model that allows mapping domain objects to a data row. The mapping metadata model is populated by using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The MappingR2dbcConverter also lets you map objects to rows without providing any additional metadata, by following a set of conventions.

本节介绍 MappingR2dbcConverter 的功能,包括如何使用约定将对象映射到行以及如何使用基于注释的映射元数据覆盖这些约定。

This section describes the features of the MappingR2dbcConverter, including how to use conventions for mapping objects to rows and how to override those conventions with annotation-based mapping metadata.

在继续本章之前,阅读有关 object-mapping.adoc的基础知识。

Read on the basics about object-mapping.adoc before continuing with this chapter.

Convention-based Mapping

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

MappingR2dbcConverter has a few conventions for mapping objects to rows when no additional mapping metadata is provided. The conventions are:

  • The short Java class name is mapped to the table name in the following manner. The com.bigbank.SavingsAccount class maps to the SAVINGS_ACCOUNT table name. The same name mapping is applied for mapping fields to column names. For example, the firstName field maps to the FIRST_NAME column. You can control this mapping by providing a custom NamingStrategy. See mapping.configuration for more detail. Table and column names that are derived from property or class names are used in SQL statements without quotes by default. You can control this behavior by setting RelationalMappingContext.setForceQuote(true).

  • Nested objects are not supported.

  • The converter uses any Spring Converters registered with CustomConversions to override the default mapping of object properties to row columns and values.

  • The fields of an object are used to convert to and from columns in the row. Public JavaBean properties are not used.

  • If you have a single non-zero-argument constructor whose constructor argument names match top-level column names of the row, that constructor is used. Otherwise, the zero-argument constructor is used. If there is more than one non-zero-argument constructor, an exception is thrown. Refer to Object Creation for further details.

Mapping Configuration

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

By default, (unless explicitly configured) an instance of MappingR2dbcConverter is created when you create a DatabaseClient. You can create your own instance of the MappingR2dbcConverter. By creating your own instance, you can register Spring converters to map specific classes to and from the database.

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

You can configure the MappingR2dbcConverter as well as DatabaseClient and ConnectionFactory by using Java-based metadata. The following example uses Spring’s Java-based configuration:

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

If you set setForceQuote of the R2dbcMappingContext to true, table and column names derived from classes and properties are used with database specific quotes. This means that it is OK to use reserved SQL words (such as order) in these names. You can do so by overriding r2dbcMappingContext(Optional<NamingStrategy>) of AbstractR2dbcConfiguration. Spring Data converts the letter casing of such a name to that form which is also used by the configured database when no quoting is used. Therefore, you can use unquoted names when creating tables, as long as you do not use keywords or special characters in your names. For databases that adhere to the SQL standard, this means that names are converted to upper case. The quoting character and the way names get capitalized is controlled by the used Dialect. See R2DBC Drivers for how to configure custom dialects.

@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 的方法。

AbstractR2dbcConfiguration requires you to implement a method that defines a ConnectionFactory.

可以通过覆盖 r2dbcCustomConversions 方法向转换器添加其他转换器。

You can add additional converters to the converter by overriding the r2dbcCustomConversions method.

您可以通过将其注册为 Bean 来配置自定义 NamingStrategyNamingStrategy 控制类和属性的名称如何转换为表和列的名称。

You can configure a custom NamingStrategy by registering it as a bean. The NamingStrategy controls how the names of classes and properties get converted to the names of tables and columns.

AbstractR2dbcConfiguration 创建了一个 DatabaseClient 实例,并使用 databaseClient 的名称将它注册到容器。

AbstractR2dbcConfiguration creates a DatabaseClient instance and registers it with the container under the name of databaseClient.

Metadata-based Mapping

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

To take full advantage of the object mapping functionality inside the Spring Data R2DBC support, you should annotate your mapped objects with the @Table annotation. Although it is not necessary for the mapping framework to have this annotation (your POJOs are mapped correctly, even without any annotations), it lets the classpath scanner find and pre-process your domain objects to extract the necessary metadata. If you do not use this annotation, your application takes a slight performance hit the first time you store a domain object, because the mapping framework needs to build up its internal metadata model so that it knows about the properties of your domain object and how to persist them. The following example shows a domain object:

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 注释告诉映射器您希望使用哪个属性作为主键。

The @Id annotation tells the mapper which property you want to use as the primary key.

Default Type Mapping

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

The following table explains how property types of an entity affect mapping:

Source Type Target Type Remarks

Primitive types and wrapper types

Passthru

Can be customized using mapping.explicit.converters.

JSR-310 Date/Time types

Passthru

Can be customized using mapping.explicit.converters.

String, BigInteger, BigDecimal, and UUID

Passthru

Can be customized using mapping.explicit.converters.

Enum

String

Can be customized by registering mapping.explicit.converters.

Blob and Clob

Passthru

Can be customized using mapping.explicit.converters.

byte[], ByteBuffer

Passthru

Considered a binary payload.

Collection<T>

Array of T

Conversion to Array type if supported by the configured driver, not supported otherwise.

Arrays of primitive types, wrapper types and String

Array of wrapper type (e.g. int[]Integer[])

Conversion to Array type if supported by the configured driver, not supported otherwise.

Driver-specific types

Passthru

Contributed as a simple type by the used R2dbcDialect.

Complex objects

Target type depends on registered Converter.

Requires a mapping.explicit.converters, not supported otherwise.

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

The native data type for a column depends on the R2DBC driver type mapping. Drivers can contribute additional simple types such as Geometry types.

Mapping Annotation Overview

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

The RelationalConverter can use metadata to drive the mapping of objects to rows. The following annotations are available:

  • @Id: Applied at the field level to mark the primary key.

  • @Table: Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the table where the database is stored.

  • @Transient: By default, all fields are mapped to the row. This annotation excludes the field where it is applied from being stored in the database. Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.

  • @PersistenceCreator: Marks a given constructor or static factory method — even a package protected one — to use when instantiating the object from the database. Constructor arguments are mapped by name to the values in the retrieved row.

  • @Value: This annotation is part of the Spring Framework. Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key’s value retrieved in the database before it is used to construct a domain object. In order to reference a column of a given row one has to use expressions like: @Value("#root.myProperty") where root refers to the root of the given Row.

  • @Column: Applied at the field level to describe the name of the column as it is represented in the row, letting the name be different from the field name of the class. Names specified with a @Column annotation are always quoted when used in SQL statements. For most databases, this means that these names are case-sensitive. It also means that you can use special characters in these names. However, this is not recommended, since it may cause problems with other tools.

  • @Version: Applied at field level is used for optimistic locking and checked for modification on save operations. The value is null (zero for primitive types) is considered as marker for entities to be new. The initially stored value is zero (one for primitive types). The version gets incremented automatically on every update.

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

The RelationalConverter can use metadata to drive the mapping of objects to rows. The following annotations are available:

  • @Id: Applied at the field level to mark the primary key.

  • @Table: Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the table where the database is stored.

  • @Transient: By default, all fields are mapped to the row. This annotation excludes the field where it is applied from being stored in the database. Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.

  • @PersistenceCreator: Marks a given constructor or static factory method — even a package protected one — to use when instantiating the object from the database. Constructor arguments are mapped by name to the values in the retrieved row.

  • @Value: This annotation is part of the Spring Framework. Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key’s value retrieved in the database before it is used to construct a domain object. In order to reference a column of a given row one has to use expressions like: @Value("#root.myProperty") where root refers to the root of the given Row.

  • @Column: Applied at the field level to describe the name of the column as it is represented in the row, letting the name be different from the field name of the class. Names specified with a @Column annotation are always quoted when used in SQL statements. For most databases, this means that these names are case-sensitive. It also means that you can use special characters in these names. However, this is not recommended, since it may cause problems with other tools.

  • @Version: Applied at field level is used for optimistic locking and checked for modification on save operations. The value is null (zero for primitive types) is considered as marker for entities to be new. The initially stored value is zero (one for primitive types). The version gets incremented automatically on every update. See Optimistic Locking for further reference.

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

The mapping metadata infrastructure is defined in the separate spring-data-commons project that is technology-agnostic. Specific subclasses are used in the R2DBC support to support annotation based metadata. Other strategies can also be put in place (if there is demand).

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] 来调整它。

By convention, Spring Data applies a NamingStrategy to determine table, column, and schema names defaulting to snake case. An object property named firstName becomes first_name. You can tweak that by providing a {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/NamingStrategy.html[NamingStrategy] in your application context.

Override table names

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

When the table naming strategy does not match your database table names, you can override the table name with the {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Table.html[@Table] annotation. The element value of this annotation provides the custom table name. The following example maps the MyEntity class to the CUSTOM_TABLE_NAME table in the database:

@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`列:

When the column naming strategy does not match your database table names, you can override the table name with the {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Column.html[@Column] annotation. The element value of this annotation provides the custom column name. The following example maps the name property of the MyEntity class to the CUSTOM_COLUMN_NAME column in the database:

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

Read Only Properties

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

Attributes annotated with @ReadOnlyProperty will not be written to the database by Spring Data, but they will be read when an entity gets loaded.

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

Spring Data will not automatically reload an entity after writing it. Therefore, you have to reload it explicitly if you want to see data that was generated in the database for such columns.

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

If the annotated attribute is an entity or collection of entities, it is represented by one or more separate rows in separate tables. Spring Data will not perform any insert, delete or update for these rows.

Insert Only Properties

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

Attributes annotated with @InsertOnlyProperty will only be written to the database by Spring Data during insert operations. For updates these properties will be ignored.

@InsertOnlyProperty 仅受支持于聚合根。

@InsertOnlyProperty is only supported for the aggregate root.

Customized Object Construction

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

The mapping subsystem allows the customization of the object construction by annotating a constructor with the @PersistenceConstructor annotation.The values to be used for the constructor parameters are resolved in the following way:

  • If a parameter is annotated with the @Value annotation, the given expression is evaluated, and the result is used as the parameter value.

  • If the Java type has a property whose name matches the given field of the input row, then its property information is used to select the appropriate constructor parameter to which to pass the input field value. This works only if the parameter name information is present in the Java .class files, which you can achieve by compiling the source with debug information or using the -parameters command-line switch for javac in Java 8.

  • Otherwise, a MappingException is thrown to indicate that the given constructor parameter could not be bound.

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 实例完成大部分工作,但让您有选择地处理特定类型的转换——也许是为了优化性能。

When storing and querying your objects, it is often convenient to have a R2dbcConverter instance to handle the mapping of all Java types to OutboundRow instances. However, you may sometimes want the R2dbcConverter instances to do most of the work but let you selectively handle the conversion for a particular type — perhaps to optimize performance.

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

To selectively handle the conversion yourself, register one or more one or more org.springframework.core.convert.converter.Converter instances with the R2dbcConverter.

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

You can use the r2dbcCustomConversions method in AbstractR2dbcConfiguration to configure converters. The examples at the beginning of this chapter show how to perform the configuration with Java.

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

Custom top-level entity conversion requires asymmetric types for conversion. Inbound data is extracted from R2DBC’s Row. Outbound data (to be used with INSERT/UPDATE statements) is represented as OutboundRow and later assembled to a statement.

以下 Spring 转换器实现的示例将 Row 转换为 Person POJO:

The following example of a Spring Converter implementation converts from a Row to a 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)。

Please note that converters get applied on singular properties. Collection properties (e.g. Collection<Person>) are iterated and converted element-wise. Collection converters (e.g. Converter<List<Person>>, OutboundRow) are not supported.

R2DBC 使用装箱原语(Integer.class 而不是 int.class)来返回基元值。

R2DBC uses boxed primitives (Integer.class instead of int.class) to return primitive values.

以下示例将 Person 转换为 OutboundRow

The following example converts from a Person to a 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()`转换。此外,你需要在驱动程序级别配置枚举类型,以便驱动程序知道如何表示枚举类型。

Some databases, such as Postgres, can natively write enum values using their database-specific enumerated column type. Spring Data converts Enum values by default to String values for maximum portability. To retain the actual enum value, register a @Writing converter whose source and target types use the actual enum type to avoid using Enum.name() conversion. Additionally, you need to configure the enum type on the driver level so that the driver is aware how to represent the enum type.

以下示例显示了用于本机读取和写入 Color 枚举值的组件:

The following example shows the involved components to read and write Color enum values natively:

enum Color {
    Grey, Blue
}

class ColorConverter extends EnumWriteSupport<Color> {

}


class Product {
    @Id long id;
    Color color;

    // …
}