此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Data Relational 3.5.2! |
映射
丰富的映射支持由MappingR2dbcConverter
.MappingR2dbcConverter
具有丰富的元数据模型,允许将域对象映射到数据行。
映射元数据模型是通过使用域对象上的注释来填充的。
但是,基础设施不仅限于使用注释作为元数据信息的唯一来源。
这MappingR2dbcConverter
还允许您通过遵循一组约定将对象映射到行,而无需提供任何其他元数据。
本节介绍MappingR2dbcConverter
,包括如何使用约定将对象映射到行,以及如何使用基于注释的映射元数据覆盖这些约定。
在继续本章之前,请先阅读有关对象映射基础知识的基础知识。
基于约定的映射
MappingR2dbcConverter
在未提供其他映射元数据时,有一些用于将对象映射到行的约定。
约定是:
-
短 Java 类名按以下方式映射到表名。 这
com.bigbank.SavingsAccount
类映射到SAVINGS_ACCOUNT
表名。 将字段映射到列名时应用相同的名称映射。 例如,firstName
字段映射到FIRST_NAME
列。 您可以通过提供自定义NamingStrategy
. 有关更多详细信息,请参阅映射配置。 默认情况下,从属性或类名称派生的表名和列名在 SQL 语句中使用,不带引号。 您可以通过设置RelationalMappingContext.setForceQuote(true)
. -
不支持嵌套对象。
-
转换器使用注册的任何 Spring 转换器
CustomConversions
以覆盖对象属性到行列和值的默认映射。 -
对象的字段用于在行中的列之间进行转换。 公共
JavaBean
不使用属性。 -
如果有一个非零参数构造函数,其构造函数参数名称与行的顶级列名匹配,则使用该构造函数。 否则,使用零参数构造函数。 如果有多个非零参数构造函数,则会引发异常。 有关更多详细信息,请参阅对象创建。
映射配置
默认情况下,(除非显式配置)的实例MappingR2dbcConverter
创建DatabaseClient
.
您可以创建自己的MappingR2dbcConverter
.
通过创建自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库或从数据库映射特定类。
您可以配置MappingR2dbcConverter
以及DatabaseClient
和ConnectionFactory
通过使用基于 Java 的元数据。
以下示例使用 Spring 基于 Java 的配置:
如果您将setForceQuote
的R2dbcMappingContext to
true,则派生自类和属性的表名和列名与特定于数据库的引号一起使用。
这意味着可以在这些名称中使用保留的 SQL 字(例如 order)。
您可以通过重写r2dbcMappingContext(Optional<NamingStrategy>)
之AbstractR2dbcConfiguration
.
Spring Data将此类名称的字母大小写转换为在未使用引号时配置的数据库也使用该形式。
因此,您可以在创建表时使用不带引号的名称,只要您在名称中不使用关键字或特殊字符即可。
对于遵循 SQL 标准的数据库,这意味着名称将转换为大写。
引用字符和名称大写的方式由使用的Dialect
.
有关如何配置自定义方言,请参阅 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 也会正确映射),但它允许类路径扫描程序查找并预处理您的域对象以提取必要的元数据。
如果不使用此注释,则应用程序在首次存储域对象时会略有性能受到影响,因为映射框架需要构建其内部元数据模型,以便它知道域对象的属性以及如何持久化它们。
以下示例显示了一个域对象:
@Table
public class Person {
@Id
private Long id;
private Integer ssn;
private String firstName;
private String lastName;
}
这@Id 注释告诉映射器您要使用哪个属性作为主键。 |
默认类型映射
下表说明了实体的属性类型如何影响映射:
源类型 | 目标类型 | 言论 |
---|---|---|
基元类型和包装器类型 |
直通 |
可以使用显式转换器进行自定义。 |
JSR-310 日期/时间类型 |
直通 |
可以使用显式转换器进行自定义。 |
|
直通 |
可以使用显式转换器进行自定义。 |
|
字符串 |
可以通过注册显式转换器进行自定义。 |
|
直通 |
可以使用显式转换器进行自定义。 |
|
直通 |
被视为二进制有效负载。 |
|
数组 |
如果配置的驱动程序支持,则转换为阵列类型,否则不支持。 |
原始类型、包装器类型和 |
包装器类型的数组(例如 |
如果配置的驱动程序支持,则转换为阵列类型,否则不支持。 |
特定于驱动程序的类型 |
直通 |
由 used 作为简单类型贡献 |
复杂对象 |
目标类型取决于注册 |
需要显式转换器,否则不支持。 |
列的本机数据类型取决于 R2DBC 驱动程序类型映射。 驱动程序可以贡献其他简单类型,例如 Geometry 类型。 |
映射注释概述
这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 Expression Language 语句在用于构造域对象之前转换在数据库中检索到的键值。 为了引用给定行的列,必须使用以下表达式:@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
class 添加到CUSTOM_TABLE_NAME
表:
@Table("CUSTOM_TABLE_NAME")
class MyEntity {
@Id
Integer id;
String name;
}
您可以使用 Spring Data 的 SpEL 支持来动态创建表名。 生成表名后,将被缓存,因此它仅在每个映射上下文中是动态的。
覆盖列名称
当列命名策略与数据库表名不匹配时,您可以使用Column
注解。
元素value
提供自定义列名。
以下示例映射了name
属性的MyEntity
class 添加到CUSTOM_COLUMN_NAME
列:
class MyEntity {
@Id
Integer id;
@Column("CUSTOM_COLUMN_NAME")
String name;
}
您可以使用 Spring Data 的 SpEL 支持来动态创建列名。 生成后,名称将被缓存,因此它仅在每个映射上下文中是动态的。
嵌入式 ID
标识符属性可以使用@Embedded
允许使用复合 ID。
完整的嵌入实体被视为 id,因此用于确定聚合是否被视为需要插入的新聚合或现有聚合的检查,请求更新是基于该实体,而不是其元素。
大多数用例都需要自定义BeforeConvertCallback
以设置新聚合的 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) {
}
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 不会对这些行执行任何插入、删除或更新。
定制对象构建
映射子系统允许通过使用@PersistenceConstructor
注解。要用于构造函数参数的值按以下方式解析:
-
如果参数用
@Value
注释,对给定的表达式进行求值,并将结果用作参数值。 -
如果 Java 类型具有名称与输入行的给定字段匹配的属性,则其属性信息用于选择要将输入字段值传递到的相应构造函数参数。 仅当参数名称信息存在于 Java 中时,这才有效
.class
文件,您可以通过使用调试信息编译源代码或使用-parameters
命令行开关javac
在 Java 8 中。 -
否则,一个
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
实例来完成大部分工作,但允许您有选择地处理特定类型的转换——也许是为了优化性能。
要有选择地自行处理转换,请注册一个或多个org.springframework.core.convert.converter.Converter
实例与R2dbcConverter
.
您可以使用r2dbcCustomConversions
方法AbstractR2dbcConfiguration
以配置转换器。
本章开头的示例展示了如何使用 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;
// …
}