Spring Data Commons对象映射基础

原标题:Spring认证|Spring Data Commons对象映射基础

本节涵盖 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不变性的基础知识。请注意,本节仅适用于不使用底层数据存储(如 JPA)的对象映射的 Spring Data 模块。此外,请务必查阅特定于存储的部分以获取特定于存储的对象映射,例如索引、自定义列或字段名称等。

Spring Data 对象映射的核心职责是创建域对象的实例并将存储本机数据结构映射到这些实例上。这意味着我们需要两个基本步骤:

使用公开的构造函数之一创建实例。

实例填充以实现所有公开的属性。

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 将通过反射回退到实体实例化。

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;

}

}

示例 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. 相反,属性的默认设置是在工厂方法中处理的。

3. 一般建议

尽量坚持不可变对象 ——不可变对象很容易创建,因为具体化一个对象只是调用它的构造函数的问题。此外,这避免了您的域对象被允许客户端代码操作对象状态的 setter 方法所困扰。如果您需要这些,最好将它们打包保护,以便它们只能由有限数量的并置类型调用。仅构造函数实现比属性填充快 30%。

提供一个全参数构造函数 ——即使你不能或不想将你的实体建模为不可变值,提供一个将实体的所有属性(包括可变属性)作为参数的构造函数仍然是有价值的,因为这允许对象映射以跳过属性填充以获得最佳性能。

使用工厂方法而不是重载构造函数来避免@PersistenceConstructor ——为了获得最佳性能需要一个全参数构造函数,我们通常希望公开更多应用程序用例特定的构造函数,这些构造函数省略自动生成的标识符等。这是一种既定的模式,而不是使用静态工厂方法来公开 all-args 构造函数的这些变体。

确保遵守允许使用生成的实例化器和属性访问器类的约束 —— 

对于要生成的标识符,仍然使用 final 字段与全参数持久性构造函数(首选)或with…方法相结合 —— 

使用 Lombok 避免样板代码 ——由于持久性操作通常需要一个接受所有参数的构造函数,因此它们的声明变成了对字段分配的样板参数的乏味重复,而使用 Lombok 的@AllArgsConstructor.

4. 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(…)创建新对象实例的方法,该方法从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。

(0)

相关推荐

  • 科技·Kotlin从入门到精通,类和对象

    类和对象是两种以计算机为载体的计算机语言的合称.对象是对客观事物的抽象,类是对对象的抽象.类是一种抽象的数据类型.它们的关系是,对象是类的实例,类是对象的模板.类定义Kotlin类可以包含:构造函数和 ...

  • C# 9.0新特性详解系列之一:只初始化设置器(init only setter)

    C# 9.0新特性详解系列之一:只初始化设置器(init only setter)

  • JavaScript 函数 Apply

    方法重用 通过 apply() 方法,您能够编写用于不同对象的方法. JavaScript apply() 方法 apply() 方法与 call() 方法非常相似: 在本例中,person 的 fu ...

  • Spring Data Commons 示例查询

    原标题:Spring认证|Spring Data Commons 示例查询 6.1. 介绍 本章介绍了 Query by Example 并解释了如何使用它. 示例查询 (QBE) 是一种用户友好的查 ...

  • Spring Data Commons审计

    原标题:Spring认证|Spring Data Commons审计 7.1.基本 Spring Data 提供了复杂的支持,以透明地跟踪谁创建或更改了实体以及更改发生的时间.要从功能中获益,您必须为 ...

  • Solr和Spring Data Solr

    一.Solr概述与安装 1.Solr简介 Solr是一个开源搜索平台,用于构建搜索应用程序. 它建立在Lucene(全文搜索引擎)之上. Solr是企业级的,快速的和高度可扩展的.Solr可以和Had ...

  • Github14k堪称神级的Spring Boot手册,从基础入门到实战进阶

    一直建议Java程序员必须把springboot搞清楚,现在企业中Springboot太重要了,不管是大互联网公司比如阿里,还是小公司都在用springboot,现在分享一份学习资料希望能帮到大家. ...

  • 对象到对象映射-AutoMapper

    概述 AutoMapper 是一个对象-对象映射器,可以将一个对象映射到另一个对象. 用来解决一个看似复杂的问题,这种类型的代码编写起来相当枯燥乏味, 官网地址: http://automapper. ...

  • 什么是Spring Data?

    Spring Data 的委托是为数据访问提供熟悉且符合 Spring 的编程模型,同时仍保留着相关数据存储的特殊特征. 它使使用数据访问技术.关系和非关系数据库.map-reduce 框架和基于云的 ...

  • Spring Data Elasticsearch的使用

    最近在项目开发过程中发现项目接口调用日志表存在一定的问题,为了记录项目中所有的接口调用数据专门用了一个表来存储请求接口的报文信息,一直以来也没出现什么问题,上次我在和外部系统对接时发现,该接口返回的数 ...

  • Spring Data JDBC - 如何使用自定义 ID 生成

    原标题:Spring认证|Spring Data JDBC-如何使用自定义ID生成 这是关于如何解决使用 Spring Data JDBC 时可能遇到的各种挑战的系列文章的第一篇. 如果你不了解 Sp ...

  • Spring Data JDBC、引用和聚合

    之前的博客文章中,我.描述了如何设置和使用 Spring Data JDBC.我还描述了使 Spring Data 原标题:Spring认证|Spring Data JDBC.引用和聚合 JDBC 比 ...