Mapping
BasicJdbcConverter 是一款强大的工具,它可以通过注释或约定从域对象映射到数据库行。它支持各种类型,包括基本类型、枚举、日期、列表和映射,以及对其他实体的引用。通过提供自定义转换器,可以覆盖默认映射并支持特定的数据类型转换。提及对聚合根的引用以及管理反向引用的最佳实践。
丰富的映射支持是由 BasicJdbcConverter
提供的。BasicJdbcConverter
有一个丰富的元数据模型,允许将域对象映射到数据行。映射元数据模型通过使用注释填充到你的域对象上。然而,基础架构并不局限于使用注释作为元数据信息的唯一来源。通过遵循一组约定,BasicJdbcConverter
还允许你在不提供任何其他元数据的情况下将对象映射到行。
本部分描述了 BasicJdbcConverter
的功能,包括如何使用约定将对象映射到行以及如何用基于注释的映射元数据覆盖这些约定。
在继续本章之前,阅读有关 object-mapping.adoc的基础知识。
Convention-based Mapping
当没有提供其他映射元数据时,BasicJdbcConverter
有几个约定可将对象映射到行。这些约定是:
-
短 Java 类名以以下方式映射到表名。
com.bigbank.SavingsAccount
类映射到SAVINGS_ACCOUNT
表名。相同的名称映射用于将字段映射到列名称。例如,firstName
字段映射到FIRST_NAME
列。您可以通过提供自定义NamingStrategy
来控制此映射。有关更多详细信息,请参见 Mapping Configuration。从属性或类名派生的表名和列名在 SQL 语句中默认不带引号。您可以通过设置RelationalMappingContext.setForceQuote(true)
来控制此行为。 -
该转换器使用已使用
CustomConversions
注册的任何 Spring 转换器来覆盖对象属性到行列和值的默认映射。 -
对象的字段用于转换到和从行中的列。公共
JavaBean
属性未使用。 -
如果你只有一个非零参数构造函数,且其构造函数参数名称匹配行的顶级列名称,则使用该构造函数。否则,将使用零参数构造函数。如果有多于一个非零参数构造函数,则会引发异常。请参阅 Object Creation 了解更多详细信息。
Supported Types in Your Entity
目前支持以下类型的属性:
-
所有基本类型及其装箱类型(
int
,float
,Integer
,`Float`等等) -
枚举映射到它们的名称。
-
String
-
java.util.Date
,java.time.LocalDate
,java.time.LocalDateTime`和 `java.time.LocalTime
-
如果你的数据库支持,则上述类型的数组和集合可以映射到阵列类型的列。
-
数据库驱动程序接受的任何内容。
-
对其他实体的引用。它们被视为一对一的关系或嵌套类型。一对一关系实体不必有
id
属性。引用实体的表预期有一个附加列,其名称基于引用实体,请参见 Back References。嵌套实体不需要id
。如果存在一个嵌套实体,则它会被映射为一个没有任何特殊含义的常规属性。 -
Set<some entity>
被视为一对多关系。引用实体的表预期有一个附加列,其名称基于引用实体,请参见 Back References。 -
Map<simple type, some entity>
被视为一个限定的一对多关系。引用实体的表预期有两个附加列:一个以引用实体来为外键命名(请参见 Back References),另一个具有相同名称和一个附加_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
指定一个键列没有任何作用。
Mapping Annotation Overview
`RelationalConverter`可以使用元数据来驱动对象到行的映射。下列注解是可用的:
-
@Id
:应用于字段级别以标记主键。 -
@Table
:应用于类级别以指示 此类 是映射到数据库的候选者。您可以指定存储数据库的表格的名称。 -
@Transient
:默认情况下,所有字段都映射到行。这个批注将应用其排除字段从存储在数据库中。瞬态属性不能在持久化构造函数中使用,因为转换器无法使构造函数实参的值具体化。 -
@PersistenceCreator
:标记一个给定的构造函数,甚至是包保护的,或者静态工厂方法 — 使用的时候从数据库实例化对象。构造函数实参通过名称映射到检索行中的值。 -
@Value
:这个批注是 Spring 框架的一部分。在映射框架中它可以应用到构造函数实参。这使您能够使用 Spring 表达式语言语句来转换在数据库中检索的密钥值,然后才能用于构造域对象。为了引用给定行的列,必须使用以下表达式:@Value("#root.myProperty")
,其中 root 指向给定Row
的根。 -
@Column
:应用于字段层级,用于描述列的名称,使其在行中显示为指定名称,允许该名称不同于类的字段名称。使用@Column
注释指定的名称在 SQL 语句中使用时,始终会被引用。对于大多数数据库,这意味着这些名称区分大小写。这意味着你可以在这些名称中使用特殊字符。然而,不建议这样做,因为它可能会给其他工具带来麻烦。 -
@Version
:应用于字段层级,用于乐观锁且在保存操作中进行修改检查。值是null
(对于原始类型为zero
),被视为新实体的标记。初始存储的值是zero
(对于原始类型为one
)。版本在每次更新时都会自动增加。
有关更多参考信息,请参见 Optimistic Locking。
映射元数据基础设施在独立且与技术无关的 spring-data-commons
项目中定义。在 JDBC 支持中使用特定子类来支持基于注释的元数据。也可以实施其他策略(如果有需求)。
Referenced Entities
对引用的实体的处理是有限的。这是基于上述聚合根的思想。如果您引用另一个实体,则该实体根据定义是聚合的一部分。因此,如果您删除引用,则先前引用的实体将被删除。这也意味着引用是一对一或一对多,但不是多对一或多对多。
如果您有 n 对 1 或 n 对多引用,则根据定义,您正在处理两个单独的聚合。它们之间的引用可以编码为简单的 id
值,这些值可以通过 Spring Data JDBC 适当地映射。对这些引用进行编码的更好方法是将其设为 AggregateReference
的实例。AggregateReference
是对 id 值的包装,用于将该值标记为对其他聚合的引用。此外,将该聚合的类型编码在类型参数中。
Back References
聚合中的所有引用会导致数据库中相反方向的外键关系。默认情况下,外键列的名称是引用实体的表名。
或者,您可以选择使它们由引用实体的实体名称命名,而忽略 @Table
注释。通过在 RelationalMappingContext
上调用 setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING)
来激活此行为。
对于 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
中完成此操作,并将值存储在暂态值中。
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
] 来调整它。
Override table names
当表命名策略不匹配您的数据库表名称时,您可以使用 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Table.html[@Table
] 注解覆盖表名称。此注解的 `value`元素提供了自定义表名称。以下示例将 `MyEntity`类映射到数据库中的 `CUSTOM_TABLE_NAME`表:
@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`列:
class MyEntity {
@Id
Integer id;
@Column("CUSTOM_COLUMN_NAME")
String name;
}
Read Only Properties
使用 @ReadOnlyProperty
注解的属性不会被 Spring Data 写入到数据库中,但当加载实体时,这些属性将会被读取。
Spring Data 不会自动在写入实体后重新加载此实体。因此,如果你想要查看在数据库中为这些列生成的数据,那么就必须显式地重新加载此实体。
如果带注解的属性是实体或实体集合,则它将由各单独表中的一个或者若干单独行来表示。Spring Data 不会针对这些行执行任何插入、删除或者更新。
Insert Only Properties
使用 @InsertOnlyProperty
注解的属性仅在插入操作期间由 Spring Data 写入到数据库中。对于更新,将忽略这些属性。
@InsertOnlyProperty
仅受支持于聚合根。
Customized Object Construction
映射子系统允许通过使用 @PersistenceConstructor
注解注解构造函数来自定义对象构造。用于构造函数参数的值将按照以下方式解析:
-
如果用
@Value
批注标注一个参数,则求值给定的表达式,结果用作参数值。 -
如果 Java 类型具有与其输入行给定字段匹配的名称的属性,则其属性信息用于选择将输入字段值传递到的合适的构造函数参数。这仅在 Java 8 中的 Java
.class
文件中存在参数名称信息时有效,您可以通过编译带有调试信息的源或使用javac
的-parameters
命令行开关来实现。 -
否则,将抛出
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
}
Overriding Mapping with Explicit Converters
Spring Data 允许注册自定义转换器,以影响值在数据库中的映射方式。目前,转换器仅应用于属性级别,即您只能将域中的单个值转换为数据库中的单个值,反之亦然。不支持在复杂对象和多列之间进行转换。
Writing a Property by Using a Registered Spring Converter
以下示例显示了将 Boolean
对象转换为 String
值的 Converter
的实现:
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
。
Reading by Using a Spring Converter
以下示例显示了将 String
转换为 Boolean
值的 Converter
的实现:
@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 从数据库中转换应分配给 Boolean
属性的每个 String
值。
Registering Spring Converters with the JdbcConverter
class MyJdbcConfiguration extends AbstractJdbcConfiguration {
// …
@Override
protected List<?> userConverters() {
return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
}
}
在 Spring Data JDBC 的早期版本中,建议直接覆盖 |
如果您想依靠 Spring Boot 自举 Spring Data JDBC,但仍想覆盖配置的特定方面,您可能想要公开此类 Bean。对于自定义转换,您可能会选择注册一个 |