共计 8028 个字符,预计需要花费 21 分钟才能阅读完成。
原题目:Spring 认证中国教育管理中心 -Apache Geode 的 Spring 数据教程二十一(Spring 中国教育管理中心)
Apache Geode 的 Spring 数据教程二十一
9.1. 对象映射根底
本节涵盖 Spring Data 对象映射、对象创立、字段和属性拜访、可变性和不变性的基础知识。请留神,本节仅实用于不应用底层数据存储(如 JPA)的对象映射的 Spring Data 模块。此外,请务必查阅特定于存储的局部以获取特定于存储的对象映射,例如索引、自定义列或字段名称等。
Spring Data 对象映射的外围职责是创立域对象的实例并将存储本机数据结构映射到这些实例上。这意味着咱们须要两个根本步骤:
应用公开的构造函数之一创立实例。
实例填充以实现所有公开的属性。
9.1.1. 对象创立
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 将通过反射回退到实体实例化。
9.1.2. 物业人口
一旦创立了实体的实例,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;
}
}
示例 1. 生成的属性拜访器
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 尝试应用生成的属性拜访器,如果检测到限度,则回退到基于反射的拜访器。
让咱们看一下以下实体:
示例 2. 示例实体
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;
}
}
Apache Geode 的 Spring 数据教程二十一
identifier 属性是最终的,但 null 在构造函数中设置为。该类公开了一个 withId(…) 用于设置标识符的办法,例如,当一个实例插入到数据存储中并生成一个标识符时。Person 创立新实例时,原始实例放弃不变。雷同的模式通常利用于由存储管理但可能必须为持久性操作更改的其余属性。wither 办法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,并且设置该属性将被转换为创立一个利用新标识符值的新实例。
的 firstname 和 lastname 个性是通过吸气剂可能裸露一般不可变属性。
该 age 属性是不可变的,但源自该 birthday 属性。应用所示设计,数据库值将胜过默认值,因为 Spring Data 应用惟一申明的构造函数。即便用意是计算应该是首选,重要的是此构造函数也将 age 作为参数(可能会疏忽它),否则属性填充步骤将尝试设置年龄字段并因为它不可变且没有 with…办法而失败在场。
该 comment 属性是可变的,通过间接设置其字段来填充。
的 remarks 个性是可变的,并且通过设置填充 comment 间接字段或通过调用用于 setter 办法
该类公开了一个工厂办法和一个用于创建对象的构造函数。这里的核心思想是应用工厂办法而不是额定的构造函数,以防止须要通过 @PersistenceConstructor. 相同,属性的默认设置是在工厂办法中解决的。
9.1.3. 个别倡议
尽量保持不可变对象 ——不可变对象很容易创立,因为具体化一个对象只是调用它的构造函数的问题。此外,这防止了您的域对象中充斥着容许客户端代码操作对象状态的 setter 办法。如果您须要这些,最好将它们打包爱护,以便它们只能由无限数量的并置类型调用。仅构造函数实现比属性填充快 30%。
提供一个全参数构造函数 ——即便你不能或不想将你的实体建模为不可变值,提供一个将实体的所有属性作为参数的构造函数依然有价值,包含可变的,因为这容许对象映射以跳过属性填充以获得最佳性能。
应用工厂办法而不是重载构造函数来防止 @PersistenceConstructor ——为了获得最佳性能须要一个全参数构造函数,咱们通常心愿公开更多应用程序用例特定的构造函数,这些构造函数省略主动生成的标识符等。这是一种既定的模式,而不是应用动态工厂办法来公开 all-args 构造函数的这些变体。
确保恪守容许应用生成的实例化器和属性拜访器类的束缚 ——
对于要生成的标识符,依然应用 final 字段与全参数持久性构造函数(首选)或 with…办法相结合 ——
应用 Lombok 防止样板代码 ——因为持久性操作通常须要一个承受所有参数的构造函数,因而它们的申明变成了对字段调配的样板参数的乏味反复,而应用 Lombok 的 @AllArgsConstructor.
笼罩属性
Java 容许灵便设计畛域类,其中子类能够定义已在其超类中应用雷同名称申明的属性。思考以下示例:
public class SuperType {
private CharSequence field;
public SuperType(CharSequence field) {
this.field = field;
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence field) {
this.field = field;
}
}
public class SubType extends SuperType {
private String field;
public SubType(String field) {
super(field);
this.field = field;
}
@Override
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
// optional
super.setField(field);
}
}
Apache Geode 的 Spring 数据教程二十一
这两个类都定义了一个 fieldusing 可调配类型。SubType 然而暗影 SuperType.field。依据类设计,应用构造函数可能是设置 的惟一默认办法 SuperType.field。或者,调用 super.setField(…)setter 能够设置 fieldin SuperType。所有这些机制都会在某种程度上产生抵触,因为属性共享雷同的名称,但可能代表两个不同的值。如果类型不可调配,Spring Data 会跳过超类型属性。也就是说,被笼罩属性的类型必须能够调配给它的超类型属性类型能力注册为笼罩,否则超类型属性被认为是瞬态的。咱们通常倡议应用不同的属性名称。
Spring Data 模块通常反对蕴含不同值的笼罩属性。从编程模型的角度来看,须要思考以下几点:
应该保留哪个属性(默认为所有申明的属性)?您能够通过应用 正文这些属性来排除属性 @Transient。
如何示意数据存储中的属性?对不同的值应用雷同的字段 / 列名称通常会导致数据损坏,因而您应该应用明确的字段 / 列名称来正文至多一个属性。
Using@AccessType(PROPERTY)不能被应用,因为如果不对 setter 实现做任何进一步的假如,通常不能设置超级属性。
9.1.4.Kotlin 反对
Spring Data 调整了 Kotlin 的细节以容许创立和更改对象。
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。
Kotlin 数据类的属性填充
在 Kotlin 中,默认状况下所有类都是不可变的,并且须要明确的属性申明来定义可变属性。思考以下 data 类 Person:
data class Person(val id: String, val name: String)
这个类实际上是不可变的。它容许创立新实例,因为 Kotlin 生成 copy(…)创立新对象实例的办法,该办法从现有对象复制所有属性值并将作为参数提供的属性值利用到该办法。
Kotlin 笼罩属性
Kotlin 容许申明属性笼罩来扭转子类中的属性。
open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
这样的安顿出现了两个名称为 的属性 field。Kotlin 为每个类中的每个属性生成属性拜访器(getter 和 setter)。实际上,代码如下所示:
public class SuperType {
private int field;
public SuperType(int field) {
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
public final class SubType extends SuperType {
private int field;
public SubType(int field) {
super(field);
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
Apache Geode 的 Spring 数据教程二十一
getter 和 setterSubType 只在 set 上,SubType.field 而不是 SuperType.field. 在这种安顿中,应用构造函数是设置的惟一默认办法 SuperType.field。增加办法 to SubTypeset
SuperType.fieldviathis.SuperType.field = …是可能的,但不属于反对的约定。属性笼罩在某种程度上会产生抵触,因为属性共享雷同的名称但可能代表两个不同的值。咱们通常倡议应用不同的属性名称。
Spring Data 模块通常反对蕴含不同值的笼罩属性。从编程模型的角度来看,须要思考以下几点:
应该保留哪个属性(默认为所有申明的属性)?您能够通过应用 正文这些属性来排除属性 @Transient。
如何示意数据存储中的属性?对不同的值应用雷同的字段 / 列名称通常会导致数据损坏,因而您应该应用明确的字段 / 列名称来正文至多一个属性。
using@AccessType(PROPERTY)不能应用,因为不能设置超级属性。
9.2. 实体映射
Spring Data for Apache Geode 反对映射存储在区域中的实体。映射元数据是通过应用应用程序域类上的正文来定义的,如以下示例所示:
示例 3. 将域类映射到 Apache Geode Region
@Region(“People”)
public class Person {
@Id Long id;
String firstname;
String lastname;
@PersistenceConstructor
public Person(String firstname, String lastname) {
// …
}
…
}
该 @Region 注解可用于自定义 Person 存储类实例的 Region。该 @Id 注解能够用于标注应该被用作缓存区域重点,确定区域项属性。该 @PersistenceConstructor 正文有助于打消歧义多个潜在可用的构造函数,以参数并明确标识标注为构造函数的构造函数被用来结构实体。在没有或只有一个构造函数的应用程序域类中,您能够省略正文。
除了将实体存储在顶级 Region 中,实体也能够存储在 Sub-Regions 中,如下例所示:
@Region(“/Users/Admin”)
public class Admin extends User {
…
}
@Region(“/Users/Guest”)
public class Guest extends User {
…
}
请务必应用 Apache Geode 区域的残缺门路,如应用元素的 id 或 name 属性应用 Spring Data for Apache Geode XML 命名空间定义的那样 <*-region>。