此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Relational 3.4.4! |
映射
丰富的映射支持由BasicJdbcConverter
.BasicJdbcConverter
具有丰富的元数据模型,允许将域对象映射到数据行。
映射元数据模型是通过在域对象上使用注释来填充的。
但是,基础架构不仅限于使用注释作为元数据信息的唯一来源。
这BasicJdbcConverter
还允许您通过遵循一组约定将对象映射到行,而无需提供任何其他元数据。
本节介绍BasicJdbcConverter
,包括如何使用约定将对象映射到行,以及如何使用基于注释的映射元数据覆盖这些约定。
在继续阅读本章之前,请先阅读有关对象映射基础知识的基础知识。
基于约定的映射
BasicJdbcConverter
在未提供其他映射元数据时,有一些用于将对象映射到行的约定。
约定是:
-
短 Java 类名按以下方式映射到表名。 这
com.bigbank.SavingsAccount
类映射到SAVINGS_ACCOUNT
table name 的 相同的名称映射适用于将字段映射到列名称。 例如,firstName
field 映射到FIRST_NAME
列。 您可以通过提供自定义NamingStrategy
. 有关更多详细信息,请参阅 映射配置 。 默认情况下,从属性或类名称派生的表名和列名在 SQL 语句中使用,不带引号。 您可以通过设置RelationalMappingContext.setForceQuote(true)
. -
该转换器使用任何注册的 Spring Converter
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
可以使用元数据来驱动对象到行的映射。
可以使用以下注释:
-
@Id
:在字段级别应用以标记主键。 -
@Table
:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定存储数据库的表的名称。 -
@Transient
:默认情况下,所有字段都映射到该行。 此 Comments 将应用它的字段排除在数据库中。 瞬态属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。 -
@PersistenceCreator
:标记给定的构造函数或静态工厂方法(甚至是受包保护的方法)在从数据库中实例化对象时使用。 构造函数参数按名称映射到检索到的行中的值。 -
@Value
:此注释是 Spring Framework 的一部分。 在 Map 框架中,它可以应用于构造函数参数。 这允许你在使用 Spring Expression Language 语句来转换在数据库中检索到的键的值,然后再用于构造域对象。 为了引用给定行的列,必须使用如下表达式:@Value("#root.myProperty")
其中 root 引用给定Row
. -
@Column
:在字段级别应用,以描述列在行中表示的名称,使名称与类的字段名称不同。 使用@Column
annotation 在 SQL 语句中使用时,始终引用。 对于大多数数据库,这意味着这些名称区分大小写。 这也意味着您可以在这些名称中使用特殊字符。 但是,不建议这样做,因为它可能会导致其他工具出现问题。 -
@Version
:在字段级别应用用于乐观锁定,并检查保存作时的修改。 值为null
(zero
)被视为新实体的标记。 初始存储值为zero
(one
对于原始类型)。 版本在每次更新时都会自动递增。
有关进一步的参考,请参见 Optimistic Locking。
映射元数据基础架构在单独的spring-data-commons
与技术无关的项目。
JDBC 支持中使用了特定的子类来支持基于 Comments 的元数据。
也可以制定其他策略(如果有需求)。
引用的实体
对引用实体的处理是有限的。 这是基于上述聚合根的思想。 如果您引用另一个实体,则根据定义,该实体是聚合的一部分。 因此,如果您删除引用,则之前引用的实体将被删除。 这也意味着引用是 1-1 或 1-n,而不是 n-1 或 n-m。
如果您有 n-1 或 n-m 引用,则根据定义,您正在处理两个单独的聚合。
它们之间的引用可以编码为简单id
值,这些值与 Spring Data JDBC 正确映射。
对这些进行编码的更好方法是使它们成为AggregateReference
.
一AggregateReference
是 ID 值的包装器,它将该值标记为对其他聚合的引用。
此外,该聚合的类型在 type 参数中编码。
返回引用
聚合中的所有引用在数据库中都会导致相反方向的外键关系。 默认情况下,外键列的名称是引用实体的表名。
或者,您可以选择让它们以引用实体的实体名称命名,忽略@Table
附注。
您可以通过调用setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING)
在RelationalMappingContext
.
为List
和Map
引用 需要额外的列来保存 list index 或 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
并将值存储在 transient 值中。
命名策略
按照惯例, Spring Data 应用NamingStrategy
确定默认为 Snake 大小写的表、列和架构名称。
名为firstName
成为first_name
.
您可以通过提供NamingStrategy
在您的应用程序上下文中。
覆盖表名称
当表命名策略与数据库表名不匹配时,您可以使用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;
}
这MappedCollection
注释可用于引用类型 (一对一关系) 或集、列表和映射 (一对多关系)。idColumn
元素为引用另一个表中的 id 列的外键列提供自定义名称。
在以下示例中,MySubEntity
类具有NAME
列和CUSTOM_MY_ENTITY_ID_COLUMN_NAME
列的MyEntity
id 的 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
替换为 2 列id
和name
(来自EmbeddedEntity
类)是预期的。
但是,如果name
列实际上是null
在结果集中,整个属性embeddedEntity
将根据onEmpty
之@Embedded
哪null
s 对象(当所有嵌套属性都为null
.
与此行为相反USE_EMPTY
尝试使用默认构造函数或接受结果集中可为 null 参数值的构造函数创建新实例。
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 财产。 |
如果您在实体中多次需要 value 对象,这可以通过可选的prefix
元素的@Embedded
注解。
此元素表示一个前缀,并预置嵌入对象中的每个列名。
使用快捷方式
|
包含Collection
或Map
将始终被视为非空,因为它们至少包含空的 collection 或 map。
因此,这样的实体永远不会null
即使使用 @Embedded(onEmpty = USE_NULL)。
只读属性
注释@ReadOnlyProperty
不会被 Spring Data 写入数据库,但它们会在加载实体时被读取。
Spring Data 在写入实体后不会自动重新加载实体。 因此,如果要查看数据库中为此类列生成的数据,则必须显式重新加载它。
如果带注释的属性是实体或实体集合,则它由单独表中的一个或多个单独行表示。 Spring Data 不会对这些行执行任何插入、删除或更新。
Insert Only 属性
注释@InsertOnlyProperty
将仅在插入作期间由 Spring Data 写入数据库。
对于更新,将忽略这些属性。
@InsertOnlyProperty
仅支持聚合根。
自定义对象构造
映射子系统允许通过使用@PersistenceConstructor
注解。要用于 constructor 参数的值按以下方式解析:
-
如果参数使用
@Value
annotation 中,将计算给定的表达式,并将结果用作参数值。 -
如果 Java 类型具有其名称与 input 行的给定字段匹配的属性,则其属性信息用于选择要将 input 字段值传递到的相应构造函数参数。 仅当 Java 中存在参数名称信息时,此选项才有效
.class
文件,您可以通过使用调试信息编译源或使用-parameters
的命令行开关javac
在 Java 8 中。 -
否则,一个
MappingException
的 S MissAV.com 的 S Alpha 参数,以指示无法绑定给定的 constructor 参数。
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
object 设置为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";
}
}
There are a couple of things to notice here: Boolean
and String
are both simple types hence Spring Data requires a hint in which direction this converter should apply (reading or writing).
By annotating this converter with @WritingConverter
you instruct Spring Data to write every Boolean
property as String
in the database.
Reading by Using a Spring Converter
The following example shows an implementation of a Converter
that converts from a String
to a Boolean
value:
@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {
@Override
public Boolean convert(String source) {
return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
}
}
There are a couple of things to notice here: String
and Boolean
are both simple types hence Spring Data requires a hint in which direction this converter should apply (reading or writing).
By annotating this converter with @ReadingConverter
you instruct Spring Data to convert every String
value from the database that should be assigned to a Boolean
property.
Registering Spring Converters with the JdbcConverter
class MyJdbcConfiguration extends AbstractJdbcConfiguration {
// …
@Override
protected List<?> userConverters() {
return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
}
}
In previous versions of Spring Data JDBC it was recommended to directly overwrite AbstractJdbcConfiguration.jdbcCustomConversions()
.
This is no longer necessary or even recommended, since that method assembles conversions intended for all databases, conversions registered by the Dialect
used and conversions registered by the user.
If you are migrating from an older version of Spring Data JDBC and have AbstractJdbcConfiguration.jdbcCustomConversions()
overwritten conversions from your Dialect
will not get registered.
If you want to rely on Spring Boot to bootstrap Spring Data JDBC, but still want to override certain aspects of the configuration, you may want to expose beans of that type.
For custom conversions you may e.g. choose to register a bean of type JdbcCustomConversions
that will be picked up the by the Boot infrastructure.
To learn more about this please make sure to read the Spring Boot Reference Documentation.
JdbcValue
Value conversion uses JdbcValue
to enrich values propagated to JDBC operations with a java.sql.Types
type.
Register a custom write converter if you need to specify a JDBC-specific type instead of using type derivation.
This converter should convert the value to JdbcValue
which has a field for the value and for the actual JDBCType
.