共计 5221 个字符,预计需要花费 14 分钟才能阅读完成。
原题目:Spring 认证 |Spring Data Commons 对象映射根底
本节涵盖 Spring Data 对象映射、对象创立、字段和属性拜访、可变性和不变性的基础知识。请留神,本节仅实用于不应用底层数据存储(如 JPA)的对象映射的 Spring Data 模块。此外,请务必查阅特定于存储的局部以获取特定于存储的对象映射,例如索引、自定义列或字段名称等。
Spring Data 对象映射的外围职责是创立域对象的实例并将存储本机数据结构映射到这些实例上。这意味着咱们须要两个根本步骤:
应用公开的构造函数之一创立实例。
实例填充以实现所有公开的属性。
- 对象创立
Spring Data 会主动尝试检测要用于具体化该类型对象的长久实体的构造函数。解析算法的工作原理如下:
如果只有一个构造函数,则应用它。
如果有多个构造函数并且只有一个用 正文 @PersistenceConstructor,则应用它。
如果存在无参数构造函数,则应用它。其余构造函数将被疏忽。
值解析假设结构函数参数名称与实体的属性名称匹配,即解析将被执行,就像要填充属性一样,包含映射中的所有自定义(不同的数据存储列或字段名称等)。这还须要类文件中可用的参数名称信息或 @ConstructorProperties 构造函数中存在的正文。
能够通过应用 @Value 特定于商店的 SpEL 表达式应用 Spring Framework 的值正文来自定义值解析。无关更多详细信息,请参阅无关商店特定映射的局部。
对象创立外部
为了防止反射的开销,Spring Data 对象创立默认应用运行时生成的工厂类,它会间接调用域类构造函数。即对于此示例类型:
class Person {Person(String firstname, String lastname) {…}}
咱们将在运行时创立一个语义上等同于这个的工厂类:
class PersonObjectInstantiator implements ObjectInstantiator {
Object newInstance(Object… args) {
return new Person((String) args[0], (String) args[1]);
}
}
这使咱们比反射进步了大概 10% 的性能。对于有资格进行此类优化的域类,它须要恪守一组束缚:
它不能是私人课程
它不能是非动态外部类
它不能是 CGLib 代理类
Spring Data 应用的构造函数不能是公有的
如果这些条件中的任何一个匹配,Spring Data 将通过反射回退到实体实例化。
- 物业人口
一旦创立了实体的实例,Spring Data 就会填充该类的所有残余长久属性。除非实体的构造函数曾经填充(即通过其结构函数参数列表耗费),标识符属性将首先填充以容许循环对象援用的解析。之后,所有尚未由构造函数填充的非瞬态属性都在实体实例上设置。为此,咱们应用以下算法:
如果属性是不可变的但公开了一个 with…办法(见下文),咱们应用该 with…办法创立一个具备新属性值的新实体实例。
如果定义了属性拜访(即通过 getter 和 setter 拜访),咱们将调用 setter 办法。
如果属性是可变的,咱们间接设置字段。
如果属性是不可变的,咱们将应用持久性操作(请参阅对象创立)应用的构造函数来创立实例的正本。
默认状况下,咱们间接设置字段值。
财产人口外部
与咱们在对象结构中的优化相似,咱们也应用 Spring Data 运行时生成的拜访器类与实体实例进行交互。
class Person {
private final Long id;
private String firstname;
private @AccessType(Type.PROPERTY) String lastname;
Person() {
this.id = null;
}
Person(Long id, String firstname, String lastname) {
// Field assignments
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastame);
}
void setLastname(String lastname) {
this.lastname = lastname;
}
}
示例 3. 生成的属性拜访器
class PersonPropertyAccessor implements PersistentPropertyAccessor {
private static final MethodHandle firstname;
private Person person;
public void setProperty(PersistentProperty property, Object value) {
String name = property.getName();
if (“firstname”.equals(name)) {
firstname.invoke(person, (String) value);
} else if (“id”.equals(name)) {
this.person = person.withId((Long) value);
} else if (“lastname”.equals(name)) {
this.person.setLastname((String) value);
}
}
}
PropertyAccessor 持有底层对象的可变实例。这是为了启用其余不可变属性的渐变。
默认状况下,Spring Data 应用字段拜访来读取和写入属性值。依据 private 字段的可见性规定,MethodHandles 用于与字段进行交互。
该类公开了一个 withId(…) 用于设置标识符的办法,例如,当一个实例插入到数据存储中并生成一个标识符时。调用 withId(…) 创立一个新 Person 对象。所有后续渐变都将在新实例中产生,而前一个则放弃不变。
应用属性拜访容许间接办法调用而不应用 MethodHandles.
这使咱们比反射进步了大概 25% 的性能。对于有资格进行此类优化的域类,它须要恪守一组束缚:
类型不得位于默认值或 java 包下。
类型及其构造函数必须是 public
属于外部类的类型必须是 static.
应用的 Java 运行时必须容许在原始 ClassLoader. Java 9 和更新版本施加了某些限度。
默认状况下,Spring Data 会尝试应用生成的属性拜访器,并在检测到限度时回退到基于反射的拜访器。
让咱们看一下以下实体:
示例 4. 示例实体
class Person {
private final @Id Long id;
private final String firstname, lastname;
private final LocalDate birthday;
private final int age;
private String comment;
private @AccessType(Type.PROPERTY) String remarks;
static Person of(String firstname, String lastname, LocalDate birthday) {
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) {
this.remarks = remarks;
}
}
identifier 属性是最终的,但 null 在构造函数中设置为。该类公开了一个 withId(…) 用于设置标识符的办法,例如,当一个实例插入到数据存储中并生成一个标识符时。Person 创立新实例时,原始实例放弃不变。雷同的模式通常利用于由存储管理但可能必须为持久性操作更改的其余属性。wither 办法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,并且设置该属性将被转换为创立一个利用新标识符值的新实例。
的 firstname 和 lastname 个性是通过吸气剂可能裸露一般不可变属性。
该 age 属性是不可变的,但从该属性派生 birthday。应用所示设计,数据库值将胜过默认值,因为 Spring Data 应用惟一申明的构造函数。即便用意是计算应该是首选,重要的是此构造函数也将 age 作为参数(可能会疏忽它),否则属性填充步骤将尝试设置年龄字段并因为它不可变且没有 with…办法而失败在场。
该 comment 属性是可变的,通过间接设置其字段来填充。
的 remarks 个性是可变的,并且通过设置填充 comment 间接字段或通过调用用于 setter 办法
该类公开了一个工厂办法和一个用于创建对象的构造函数。这里的核心思想是应用工厂办法而不是额定的构造函数,以防止须要通过 @PersistenceConstructor. 相同,属性的默认设置是在工厂办法中解决的。
- 个别倡议
尽量保持不可变对象 ——不可变对象很容易创立,因为具体化一个对象只是调用它的构造函数的问题。此外,这防止了您的域对象被容许客户端代码操作对象状态的 setter 办法所困扰。如果您须要这些,最好将它们打包爱护,以便它们只能由无限数量的并置类型调用。仅构造函数实现比属性填充快 30%。
提供一个全参数构造函数 ——即便你不能或不想将你的实体建模为不可变值,提供一个将实体的所有属性(包含可变属性)作为参数的构造函数依然是有价值的,因为这容许对象映射以跳过属性填充以获得最佳性能。
应用工厂办法而不是重载构造函数来防止 @PersistenceConstructor ——为了获得最佳性能须要一个全参数构造函数,咱们通常心愿公开更多应用程序用例特定的构造函数,这些构造函数省略主动生成的标识符等。这是一种既定的模式,而不是应用动态工厂办法来公开 all-args 构造函数的这些变体。
确保恪守容许应用生成的实例化器和属性拜访器类的束缚 ——
对于要生成的标识符,依然应用 final 字段与全参数持久性构造函数(首选)或 with…办法相结合 ——
应用 Lombok 防止样板代码 ——因为持久性操作通常须要一个承受所有参数的构造函数,因而它们的申明变成了对字段调配的样板参数的乏味反复,而应用 Lombok 的 @AllArgsConstructor.
- Kotlin 反对
Spring Data 调整了 Kotlin 的细节以容许对象创立和变异。
4.1. Kotlin 对象创立
Kotlin 类反对实例化,默认状况下所有类都是不可变的,并且须要显式属性申明来定义可变属性。思考以下 data 类 Person:
data class Person(val id: String, val name: String)
下面的类编译为具备显式构造函数的典型类。咱们能够通过增加另一个构造函数来自定义这个类,并应用正文 @PersistenceConstructor 来批示构造函数首选项:
data class Person(var id: String, val name: String) {
@PersistenceConstructor
constructor(id: String) : this(id, “unknown”)
}
Kotlin 通过容许在未提供参数的状况下应用默认值来反对参数可选性。当 Spring Data 检测到具备参数默认值的构造函数时,如果数据存储不提供值(或简略地返回 null),它就会使这些参数不存在,因而 Kotlin 能够利用参数默认值。思考以下利用参数默认值的类 name
data class Person(var id: String, val name: String = “unknown”)
每次 name 参数不是后果的一部分或其值为 时 null,则 name 默认为 unknown。
4.2. Kotlin 数据类的属性填充
在 Kotlin 中,默认状况下所有类都是不可变的,并且须要明确的属性申明来定义可变属性。思考以下 data 类 Person:
data class Person(val id: String, val name: String)
这个类实际上是不可变的。它容许创立新实例,因为 Kotlin 生成 copy(…) 创立新对象实例的办法,该办法从现有对象复制所有属性值并将作为参数提供的属性值利用到该办法。