此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Data Relational 3.5.2! |
映射
丰富的映射支持由MappingJdbcConverter
.MappingJdbcConverter
具有丰富的元数据模型,允许将域对象映射到数据行。
映射元数据模型是通过使用域对象上的注释来填充的。
但是,基础设施不仅限于使用注释作为元数据信息的唯一来源。
这MappingJdbcConverter
还允许您通过遵循一组约定将对象映射到行,而无需提供任何其他元数据。
本节介绍MappingJdbcConverter
,包括如何使用约定将对象映射到行,以及如何使用基于注释的映射元数据覆盖这些约定。
在继续本章之前,请先阅读有关对象映射基础知识的基础知识。
基于约定的映射
MappingJdbcConverter
在未提供其他映射元数据时,有一些用于将对象映射到行的约定。
约定是:
-
短 Java 类名按以下方式映射到表名。 这
com.bigbank.SavingsAccount
类映射到SAVINGS_ACCOUNT
表名。 将字段映射到列名时应用相同的名称映射。 例如,firstName
字段映射到FIRST_NAME
列。 您可以通过提供自定义NamingStrategy
. 有关更多详细信息,请参阅映射配置。 默认情况下,从属性或类名称派生的表名和列名在 SQL 语句中使用,不带引号。 您可以通过设置RelationalMappingContext.setForceQuote(true)
. -
转换器使用注册的任何 Spring 转换器
CustomConversions
以覆盖对象属性到行列和值的默认映射。 -
对象的字段用于在行中的列之间进行转换。 公共
JavaBean
不使用属性。 -
如果有一个非零参数构造函数,其构造函数参数名称与行的顶级列名匹配,则使用该构造函数。 否则,使用零参数构造函数。 如果有多个非零参数构造函数,则会引发异常。 有关更多详细信息,请参阅对象创建。
实体中支持的类型
当前支持以下类型的属性:
-
所有基元类型及其盒装类型 (
int
,float
,Integer
,Float
,依此类推) -
枚举将映射到其名称。
-
String
-
java.util.Date
,java.time.LocalDate
,java.time.LocalDateTime
和java.time.LocalTime
-
如果您的数据库支持,则上述类型的数组和集合可以映射到数组类型的列。
-
数据库驱动程序接受的任何内容。
-
对其他实体的引用。 它们被视为一对一关系或嵌入式类型。 一对一关系实体可以选择具有
id
属性。 引用实体的表应具有附加列,其名称基于引用实体,请参阅反向引用。 嵌入实体不需要id
. 如果存在,则将其映射为没有任何特殊含义的普通属性。 -
Set<some entity>
被认为是一对多的关系。 引用实体的表应具有附加列,其名称基于引用实体,请参阅反向引用。 -
Map<simple type, some entity>
被认为是一种合格的一对多关系。 引用实体的表应有两列附加列:一列基于外键的引用实体命名(请参阅反向引用),另一列具有相同的名称和附加_key
后缀。 -
List<some entity>
映射为Map<Integer, some entity>
. 预期有相同的附加列,并且可以使用相同的方式自定义使用的名称。为
List
,Set
和Map
可以通过实现NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)
和NamingStrategy.getKeyColumn(RelationalPersistentProperty property)
分别。 或者,您可以使用@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")
. 为Set
没有效果。 -
您为其注册了合适的自定义转换器的类型。
映射注释概述
这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
与技术无关的项目。
JDBC 支持中使用了特定的子类来支持基于注释的元数据。
也可以采取其他策略(如果有需求)。
引用实体
引用实体的处理是有限的。 这是基于上述聚合根的思想。 如果引用另一个实体,则根据定义,该实体是聚合的一部分。 因此,如果删除引用,则删除以前引用的实体。 这也意味着参考是 1-1 或 1-n,但不是 n-1 或 n-m。
如果您有 n-1 或 n-m 引用,则根据定义,您正在处理两个单独的聚合。
它们之间的引用可以编码为简单的id
values,这些值与 Spring Data JDBC 正确映射。
对这些进行编码的更好方法是使它们成为AggregateReference
.
一AggregateReference
是 id 值的包装器,该值将该值标记为对不同聚合的引用。
此外,该聚合的类型在类型参数中编码。
返回参考资料
聚合中的所有引用都会导致数据库中相反方向的外键关系。 默认情况下,外键列的名称是引用实体的表名。
如果引用的 id 是@Embedded
id,则反向引用由多个列组成,每个列都由 <table-name> + + <column-name> 的串联命名。
例如,对_
Person
实体,其复合 ID 具有属性firstName
和lastName
将由两列组成PERSON_FIRST_NAME
和PERSON_LAST_NAME
.
或者,您可以选择通过引用实体的实体名称来命名它们,忽略@Table
附注。
您可以通过调用setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING)
在RelationalMappingContext
.
为List
和Map
引用需要一个额外的列来保存列表索引或映射键。
它基于外键列,并带有一个额外的_KEY
后缀。
如果您想要一种完全不同的方式来命名这些反向引用,您可以实现NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)
以适合您需求的方式。
AggregateReference
class Person {
@Id long id;
AggregateReference<Person, Long> bestFriend;
}
// ...
Person p1, p2 = // some initialization
p1.bestFriend = AggregateReference.to(p2.id);
不应在实体中包含属性来保存反向引用的实际值,也不应包含映射或列表的键列的实际值。
如果您希望这些值在您的域模型中可用,我们建议在AfterConvertCallback
并将值存储在瞬态值中。
命名策略
按照惯例,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;
}
这MappedCollection
注释可用于引用类型(一对一关系)或集合、列表和映射(一对多关系)。idColumn
元素为引用另一个表中的 id 列的外键列提供自定义名称。
在以下示例中,对应的表MySubEntity
类有一个NAME
列和CUSTOM_MY_ENTITY_ID_COLUMN_NAME
列的MyEntity
出于关系原因的 id:
class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
Set<MySubEntity> subEntities;
}
class MySubEntity {
String name;
}
使用时List
和Map
您必须有一个额外的列,用于数据集的位置List
或Map
.
可以使用keyColumn
元素MappedCollection
注解:
class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
List<MySubEntity> name;
}
class MySubEntity {
String name;
}
您可以使用 Spring Data 的 SpEL 支持来动态创建列名。 生成后,名称将被缓存,因此它仅在每个映射上下文中是动态的。
嵌入式实体
嵌入实体用于在 Java 数据模型中具有值对象,即使数据库中只有一个表也是如此。
在下面的示例中,您会看到MyEntity
映射到@Embedded
注解。
这样做的结果是,在数据库中,一个表my_entity
与两列id
和name
(从EmbeddedEntity
class) 是预期的。
但是,如果name
列实际上是null
在结果集中,整个属性embeddedEntity
将根据onEmpty
之@Embedded
哪null
s 对象,当所有嵌套属性都null
.
与此行为相反USE_EMPTY
尝试使用默认构造函数或接受结果集中可为空参数值的构造函数创建新实例。
class MyEntity {
@Id
Integer id;
@Embedded(onEmpty = USE_NULL) (1)
EmbeddedEntity embeddedEntity;
}
class EmbeddedEntity {
String name;
}
1 | Null sembeddedEntity 如果name 在null .
用USE_EMPTY 实例化embeddedEntity 具有潜力null 值name 财产。 |
如果在实体中需要多次使用值对象,则可以使用可选的prefix
元素的@Embedded
注解。
此元素表示前缀,并附加在嵌入对象中每个列名的前面。
使用快捷方式
|
包含Collection
或Map
将始终被视为非空,因为它们至少包含空的集合或映射。
因此,这样的实体永远不会null
即使使用 @Embedded(onEmpty = USE_NULL)。
嵌入式 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
}
使用显式转换器覆盖映射
Spring Data 允许注册自定义转换器以影响值在数据库中的映射方式。 目前,转换器仅应用于属性级别,即您只能将域中的单个值转换为数据库中的单个值,然后再转换回来。 不支持复杂对象和多列之间的转换。
使用已注册的 Spring 转换器编写属性
以下示例显示了Converter
从Boolean
对String
价值:
import org.springframework.core.convert.converter.Converter;
@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {
@Override
public String convert(Boolean source) {
return source != null && source ? "T" : "F";
}
}
这里有几点需要注意:Boolean
和String
都是简单类型,因此 Spring Data 需要提示此转换器应应用的方向(读取或写入)。
通过用@WritingConverter
你指示 Spring Data 编写每个Boolean
属性作为String
在数据库中。
使用弹簧转换器读取
以下示例显示了Converter
从String
设置为Boolean
价值:
@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {
@Override
public Boolean convert(String source) {
return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
}
}
这里有几点需要注意:String
和Boolean
都是简单类型,因此 Spring Data 需要提示此转换器应应用的方向(读取或写入)。
通过用@ReadingConverter
您指示 Spring Data 将每个String
应分配给Boolean
财产。
将弹簧转换器注册为JdbcConverter
class MyJdbcConfiguration extends AbstractJdbcConfiguration {
// …
@Override
protected List<?> userConverters() {
return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
}
}
在以前版本的 Spring Data JDBC 中,建议直接覆盖AbstractJdbcConfiguration.jdbcCustomConversions() .
这不再是必需的,甚至不推荐,因为该方法会汇编用于所有数据库的转换,由Dialect 用户使用和注册的转换。
如果您从旧版本的 Spring Data JDBC 迁移,并且有AbstractJdbcConfiguration.jdbcCustomConversions() 从您的Dialect 不会被注册。 |
如果您想依靠 Spring Boot 来引导 Spring Data JDBC,但仍想覆盖配置的某些方面,您可能需要公开该类型的 bean。
对于自定义转换,您可以选择注册类型为 |