对于最新的稳定版本,请使用 Spring Data Relational 3.4.0! |
映射
丰富的映射支持由 . 具有丰富的元数据模型,允许将域对象映射到数据行。
映射元数据模型是通过在域对象上使用注释来填充的。
但是,基础架构不仅限于使用注释作为元数据信息的唯一来源。
还允许您通过遵循一组约定将对象映射到行,而无需提供任何其他元数据。MappingR2dbcConverter
MappingR2dbcConverter
MappingR2dbcConverter
本节介绍了 的功能,包括如何使用约定将对象映射到行,以及如何使用基于注释的映射元数据覆盖这些约定。MappingR2dbcConverter
在继续阅读本章之前,请先阅读有关对象映射基础知识的基础知识。
基于约定的映射
MappingR2dbcConverter
在未提供其他映射元数据时,有一些用于将对象映射到行的约定。
约定是:
-
短 Java 类名按以下方式映射到表名。 类映射到表名。 相同的名称映射适用于将字段映射到列名称。 例如,字段映射到列。 您可以通过提供自定义 . 有关更多详细信息,请参阅 映射配置 。 默认情况下,从属性或类名称派生的表名和列名在 SQL 语句中使用,不带引号。 您可以通过设置 来控制此行为。
com.bigbank.SavingsAccount
SAVINGS_ACCOUNT
firstName
FIRST_NAME
NamingStrategy
RelationalMappingContext.setForceQuote(true)
-
不支持嵌套对象。
-
该转换器使用任何注册的 Spring 转换器来覆盖对象属性到行列和值的默认 Map。
CustomConversions
-
对象的字段用于与行中的列相互转换。 不使用公共属性。
JavaBean
-
如果你有一个非零参数构造函数,其构造函数参数名称与行的顶级列名匹配,则使用该构造函数。 否则,使用零参数构造函数。 如果有多个非零参数构造函数,则会引发异常。 请参阅 对象创建 以了解更多详细信息。
映射配置
默认情况下,(除非明确配置)在创建 .
您可以创建自己的 .
通过创建自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库或从数据库映射特定类。MappingR2dbcConverter
DatabaseClient
MappingR2dbcConverter
您可以使用基于 Java 的元数据配置 和 。
以下示例使用 Spring 基于 Java 的配置:MappingR2dbcConverter
DatabaseClient
ConnectionFactory
如果设置为 true,则从类和属性派生的表名和列名将与特定于数据库的引号一起使用。
这意味着可以在这些名称中使用保留的 SQL 字(例如 order)。
您可以通过重写 .
Spring Data 将此类名称的字母大小写转换为在不使用引号时配置的数据库也使用的形式。
因此,您可以在创建表时使用不带引号的名称,只要您在名称中不使用关键字或特殊字符即可。
对于遵循 SQL 标准的数据库,这意味着名称将转换为大写。
引用字符和名称大写的方式由使用的 .
有关如何配置自定义方言的信息,请参阅 R2DBC 驱动程序。setForceQuote
R2dbcMappingContext to
r2dbcMappingContext(Optional<NamingStrategy>)
AbstractR2dbcConfiguration
Dialect
@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 来配置自定义。
这些控制如何将类和属性的名称转换为表和列的名称。NamingStrategy
NamingStrategy
AbstractR2dbcConfiguration 创建一个实例,并将其注册到名为 的容器中。DatabaseClient databaseClient |
基于元数据的映射
要充分利用 Spring Data R2DBC 支持中的对象映射功能,您应该使用 Comments 注释 Map 对象。
尽管 Map 框架不需要具有此 Comments(即使没有任何 Comments,您的 POJO 也被正确映射),但它允许 Classpath 扫描器查找并预处理您的域对象以提取必要的元数据。
如果不使用此注释,则应用程序在首次存储域对象时的性能会受到轻微影响,因为映射框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何持久保存它们。
以下示例显示了一个域对象:@Table
package com.mycompany.domain;
@Table
public class Person {
@Id
private Long id;
private Integer ssn;
private String firstName;
private String lastName;
}
该注释告诉映射器要用作主键的属性。@Id |
默认类型映射
下表说明了实体的属性类型如何影响映射:
源类型 | 目标类型 | 言论 |
---|---|---|
基元类型和包装类型 |
直通 |
可以使用 Explicit Converters 进行自定义。 |
JSR-310 日期/时间类型 |
直通 |
可以使用 Explicit Converters 进行自定义。 |
|
直通 |
可以使用 Explicit Converters 进行自定义。 |
|
字符串 |
可以通过注册 Explicit Converters 进行自定义。 |
|
直通 |
可以使用 Explicit Converters 进行自定义。 |
|
直通 |
被视为二进制负载。 |
|
数组 |
转换为 Array 类型(如果配置的驱动程序支持),否则不支持。 |
原始类型、包装器类型和 |
包装器类型的数组(例如 → |
转换为 Array 类型(如果配置的驱动程序支持),否则不支持。 |
特定于驱动程序的类型 |
直通 |
由使用的 . |
复杂对象 |
目标类型取决于已注册的 。 |
需要 A Explicit Converters,否则不支持。 |
列的本机数据类型取决于 R2DBC 驱动程序类型映射。 驱动程序可以提供其他简单类型,例如 Geometry 类型。 |
映射注释概述
可以使用元数据来驱动对象到行的映射。
可以使用以下注释:RelationalConverter
-
@Id
:在字段级别应用以标记主键。 -
@Table
:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定存储数据库的表的名称。 -
@Transient
:默认情况下,所有字段都映射到该行。 此 Comments 将应用它的字段排除在数据库中。 瞬态属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。 -
@PersistenceCreator
:标记给定的构造函数或静态工厂方法(甚至是受包保护的方法)在从数据库中实例化对象时使用。 构造函数参数按名称映射到检索到的行中的值。 -
@Value
:此注释是 Spring 框架的一部分。 在 Map 框架中,它可以应用于构造函数参数。 这允许你在使用 Spring Expression Language 语句来转换在数据库中检索到的键的值,然后再用于构造域对象。 为了引用给定行的列,必须使用如下表达式: 其中 root 引用给定的根。@Value("#root.myProperty")
Row
-
@Column
:在字段级别应用,以描述列在行中表示的名称,使名称与类的字段名称不同。 在 SQL 语句中使用时,使用 annotation 指定的名称始终被引用。 对于大多数数据库,这意味着这些名称区分大小写。 这也意味着您可以在这些名称中使用特殊字符。 但是,不建议这样做,因为它可能会导致其他工具出现问题。@Column
-
@Version
:在字段级别应用用于乐观锁定,并检查保存操作的修改。 值 is ( for primitive types) 被视为新实体的标记。 最初存储的值是 ( 对于原始类型)。 版本在每次更新时都会自动递增。 有关进一步的参考,请参见 Optimistic Locking。null
zero
zero
one
制图元数据基础设施在与技术无关的单独项目中定义。
R2DBC 支持中使用了特定的子类来支持基于 Comments 的元数据。
也可以制定其他策略(如果有需求)。spring-data-commons
命名策略
按照惯例, Spring Data 应用 a 来确定 table 、 column 和 schema 名称,默认为蛇形大小写。
名为 的对象属性将变为 。
您可以通过在应用程序上下文中提供 NamingStrategy
来调整它。NamingStrategy
firstName
first_name
覆盖表名称
当表命名策略与数据库表名不匹配时,您可以使用 Table
注解覆盖表名。
此注释的元素提供自定义表名称。
以下示例将类映射到数据库中的表:value
MyEntity
CUSTOM_TABLE_NAME
@Table("CUSTOM_TABLE_NAME")
class MyEntity {
@Id
Integer id;
String name;
}
您可以使用 Spring Data 的 SPEL 支持来动态创建表名。 生成后,表名将被缓存,因此它仅根据映射上下文是动态的。
覆盖列名称
当列命名策略与您的数据库表名不匹配时,您可以使用 Column
注解覆盖表名。
此注释的元素提供自定义列名称。
下面的示例将类的属性映射到数据库中的列:value
name
MyEntity
CUSTOM_COLUMN_NAME
class MyEntity {
@Id
Integer id;
@Column("CUSTOM_COLUMN_NAME")
String name;
}
您可以使用 Spring Data 的 SPEL 支持来动态创建列名。 生成后,名称将被缓存,因此它仅对每个映射上下文是动态的。
只读属性
Spring Data 不会将带有 Comments 的属性写入数据库,但会在加载实体时读取它们。@ReadOnlyProperty
Spring Data 在写入实体后不会自动重新加载实体。 因此,如果要查看数据库中为此类列生成的数据,则必须显式重新加载它。
如果带注释的属性是实体或实体集合,则它由单独表中的一个或多个单独行表示。 Spring Data 不会对这些行执行任何插入、删除或更新。
Insert Only 属性
在插入操作期间,带有 Comments 的属性将仅由 Spring Data 写入数据库。
对于更新,将忽略这些属性。@InsertOnlyProperty
@InsertOnlyProperty
仅支持聚合根。
自定义对象构造
映射子系统允许通过使用注释注释构造函数来自定义对象构造。要用于 constructor 参数的值按以下方式解析:@PersistenceConstructor
-
如果参数使用注释进行注释,则计算给定的表达式,并将结果用作参数值。
@Value
-
如果 Java 类型具有其名称与 input 行的给定字段匹配的属性,则其属性信息用于选择要将 input 字段值传递到的相应构造函数参数。 仅当 Java 文件中存在参数名称信息时,此选项才有效,您可以通过使用调试信息编译源或使用 Java 8 中的命令行开关来实现。
.class
-parameters
javac
-
否则,将引发 a 以指示无法绑定给定的构造函数参数。
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
}
使用显式转换器覆盖映射
在存储和查询对象时,通常使用一个实例来处理所有 Java 类型到实例的映射是很方便的。
但是,您有时可能希望实例完成大部分工作,但让您有选择地处理特定类型的转换 — 也许是为了优化性能。R2dbcConverter
OutboundRow
R2dbcConverter
要有选择地自行处理转换,请使用 .org.springframework.core.convert.converter.Converter
R2dbcConverter
您可以使用 中的方法配置转换器。
本章开头的示例显示了如何使用 Java 执行配置。r2dbcCustomConversions
AbstractR2dbcConfiguration
自定义顶级实体转换需要非对称类型进行转换。
入站数据是从 R2DBC 的 中提取的。
出站数据(与 / 语句一起使用)表示为语句,然后组合成语句。Row INSERT UPDATE OutboundRow |
以下 Spring Converter 实现的示例从 a 转换为 POJO:Row
Person
@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 |
以下示例从 a 转换为 a :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;
}
}
使用 Explicit Converters 覆盖 Enum Mapping
某些数据库(如 Postgres)可以使用其特定于数据库的枚举列类型本机写入枚举值。
Spring Data 默认将值转换为值以实现最大的可移植性。
要保留实际的枚举值,请注册一个转换器,其源和目标类型使用实际的枚举类型,以避免使用 conversion。
此外,您需要在驱动程序级别配置枚举类型,以便驱动程序知道如何表示枚举类型。Enum
String
@Writing
Enum.name()
以下示例显示了在本地读取和写入 enum 值所涉及的组件:Color
enum Color {
Grey, Blue
}
class ColorConverter extends EnumWriteSupport<Color> {
}
class Product {
@Id long id;
Color color;
// …
}