Page
接口表示一个分页的结果,其中包含当前页的结果、总页数、总元素数以及分页和排序元数据。
PagedListHolder
类是一个可变的列表持有者,支持分页和排序。它提供了诸如查找指定页面的元素、获取总页数以及更改页大小等操作。
总之,本文档介绍了 Pageable
和 Page
接口以及 PagedListHolder
类,这些工具可以简化对分页和排序操作结果的处理。
Object Mapping Fundamentals
本节涵盖了 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不可变性的基础知识。请注意,此部分只适用于未使用基础数据存储的对象映射(如 JPA)的 Spring Data 模块。还要务必查阅特定于存储的部分,了解特定于存储的对象映射,如索引、自定义列或字段名称等。
This section covers the fundamentals of Spring Data object mapping, object creation, field and property access, mutability and immutability. Note, that this section only applies to Spring Data modules that do not use the object mapping of the underlying data store (like JPA). Also be sure to consult the store-specific sections for store-specific object mapping, like indexes, customizing column or field names or the like.
Spring Data 对象映射的核心职责是创建域对象实例并将存储本地数据结构映射到这些实例上。这意味着我们需要两个基本步骤:
Core responsibility of the Spring Data object mapping is to create instances of domain objects and map the store-native data structures onto those. This means we need two fundamental steps:
-
通过使用公开的其中一个构造函数创建实例。
-
Instance creation by using one of the constructors exposed.
-
实例填充以实现所有公开的属性。
-
Instance population to materialize all exposed properties.
Object creation
Spring Data 自动尝试检测一个持久实体的构造函数,以便用于实现该类型的对象。解决算法的工作方式如下:
Spring Data automatically tries to detect a persistent entity’s constructor to be used to materialize objects of that type. The resolution algorithm works as follows:
-
如果有使用
@PersistenceCreator
注释的单个静态工厂方法,则使用该方法。 -
If there is a single static factory method annotated with
@PersistenceCreator
then it is used. -
如果有单个构造函数,则使用该构造函数。
-
If there is a single constructor, it is used.
-
如果有多个构造函数,并且只有一个使用
@PersistenceCreator
注释,则使用该构造函数。 -
If there are multiple constructors and exactly one is annotated with
@PersistenceCreator
, it is used. -
如果该类型是一个 Java
Record
,则使用规范构造函数。 -
If the type is a Java
Record
the canonical constructor is used. -
如果有无参数构造函数,则会使用它。将忽略其他构造函数。
-
If there’s a no-argument constructor, it is used. Other constructors will be ignored.
值解析假定构造函数/工厂方法的参数名称与实体的属性名称匹配,即解析将执行得好像要填充该属性一样,包括映射中的所有定制(不同的数据存储列或字段名称等)。这也要求在类文件或构造函数上有 @ConstructorProperties
注释中提供参数名称信息。
The value resolution assumes constructor/factory method argument names to match the property names of the entity, i.e. the resolution will be performed as if the property was to be populated, including all customizations in mapping (different datastore column or field name etc.).
This also requires either parameter names information available in the class file or an @ConstructorProperties
annotation being present on the constructor.
可以通过使用 Spring Framework 的 @Value
值注释来使用特定于存储的 SpEL 表达式来定制值解析。请查阅有关具体存储的映射的部分以获取更多详细信息。
The value resolution can be customized by using Spring Framework’s @Value
value annotation using a store-specific SpEL expression.
Please consult the section on store specific mappings for further details.
为了避免反射时间开销,Spring Data 对象创建在默认情况下使用在运行时生成的工厂类,该类将直接调用领域类的构造函数。对于以下示例类型:
To avoid the overhead of reflection, Spring Data object creation uses a factory class generated at runtime by default, which will call the domain classes constructor directly. I.e. for this example type:
class Person {
Person(String firstname, String lastname) { … }
}
我们将在运行时为这个类创建一个语义上等价的工厂类:
we will create a factory class semantically equivalent to this one at runtime:
class PersonObjectInstantiator implements ObjectInstantiator {
Object newInstance(Object... args) {
return new Person((String) args[0], (String) args[1]);
}
}
这给了我们超过反射 10% 的迂回式性能提升。为了使领域类适用于这种优化,它需要遵守一组约束:
This gives us a roundabout 10% performance boost over reflection. For the domain class to be eligible for such optimization, it needs to adhere to a set of constraints:
-
它不得是私有类
-
it must not be a private class
-
它不得是非静态内部类
-
it must not be a non-static inner class
-
它不得是 CGLib 代理类
-
it must not be a CGLib proxy class
-
Spring Data 使用的构造函数不得为私有
-
the constructor to be used by Spring Data must not be private
如果符合上述任何一个标准,Spring Data 将回退到通过反射进行实体实例化。
If any of these criteria match, Spring Data will fall back to entity instantiation via reflection.
Property population
在创建了该实体的一个实例之后,Spring Data 填充该类的所有剩余的持久属性。除非已通过实体的构造函数填充(即通过其构造函数参数列表消耗),否则将首先填充标识符属性,以允许解析循环对象引用。在此之后,所有尚未被构造函数填充的非瞬态属性都会在实体实例上设置。为此,我们使用以下算法:
Once an instance of the entity has been created, Spring Data populates all remaining persistent properties of that class. Unless already populated by the entity’s constructor (i.e. consumed through its constructor argument list), the identifier property will be populated first to allow the resolution of cyclic object references. After that, all non-transient properties that have not already been populated by the constructor are set on the entity instance. For that we use the following algorithm:
-
如果属性为不可变但公开
with…
方法(如下所示),我们将使用with…
方法创建具有新属性值的新实体实例。 -
If the property is immutable but exposes a
with…
method (see below), we use thewith…
method to create a new entity instance with the new property value. -
如果定义了属性访问(即通过 getter 和 setter 访问),我们将调用 setter 方法。
-
If property access (i.e. access through getters and setters) is defined, we’re invoking the setter method.
-
如果属性为可变的,我们将直接设置字段。
-
If the property is mutable we set the field directly.
-
如果属性不可变,我们将使用持久化操作中使用的构造函数(见 Object creation)创建实例的副本。
-
If the property is immutable we’re using the constructor to be used by persistence operations (see mapping.object-creation) to create a copy of the instance.
-
默认情况下,将直接设置字段值。
-
By default, we set the field value directly.
类似于我们在对象构造中的优化,映射.object-creation.details,我们还使用 Spring Data 运行时生成的访问器类与实体实例进行交互。
Similarly to our mapping.object-creation.details we also use Spring Data runtime generated accessor classes to interact with the entity instance.
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;
}
}
class PersonPropertyAccessor implements PersistentPropertyAccessor {
private static final MethodHandle firstname; 2
private Person person; 1
public void setProperty(PersistentProperty property, Object value) {
String name = property.getName();
if ("firstname".equals(name)) {
firstname.invoke(person, (String) value); 2
} else if ("id".equals(name)) {
this.person = person.withId((Long) value); 3
} else if ("lastname".equals(name)) {
this.person.setLastname((String) value); 4
}
}
}
1 | PropertyAccessor 持有底层对象的不可变实例。这是为了允许对其他不可变属性进行突变。 |
2 | PropertyAccessor’s hold a mutable instance of the underlying object. This is, to enable mutations of otherwise immutable properties. |
3 | 默认情况下,Spring Data 使用字段访问来读写属性值。根据 private 域的可见性规则,MethodHandles 用于与域进行交互。 |
4 | By default, Spring Data uses field-access to read and write property values. As per visibility rules of private fields, MethodHandles are used to interact with fields. |
5 | 类公开一个 withId(…) 方法,该方法用于设置标识符,例如,当将一个实例插入到数据存储中并且生成了一个标识符时。调用 withId(…) 创建一个新的 Person 对象。所有后续的突变将在新实例中进行,而不会影响前面的实例。 |
6 | The class exposes a withId(…) method that’s used to set the identifier, e.g. when an instance is inserted into the datastore and an identifier has been generated. Calling withId(…) creates a new Person object. All subsequent mutations will take place in the new instance leaving the previous untouched. |
7 | 使用属性访问允许直接调用方法,而不使用 MethodHandles 。 |
8 | Using property-access allows direct method invocations without using MethodHandles . |
这给了我们超过反射 25% 的迂回式性能提升。为了使领域类适用于这种优化,它需要遵守一组约束:
This gives us a roundabout 25% performance boost over reflection. For the domain class to be eligible for such optimization, it needs to adhere to a set of constraints:
-
类型不得驻留在默认值或
java
包之下。 -
Types must not reside in the default or under the
java
package. -
类型及其构造函数必须为
public
-
Types and their constructors must be
public
-
类型为内部类的类型必须为
static
。 -
Types that are inner classes must be
static
. -
使用的 Java 运行时必须允许在原始
ClassLoader
中声明类。Java 9 及更新版本施加了某些限制。 -
The used Java Runtime must allow for declaring classes in the originating
ClassLoader
. Java 9 and newer impose certain limitations.
在默认情况下,Spring Data 尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的属性访问器。
By default, Spring Data attempts to use generated property accessors and falls back to reflection-based ones if a limitation is detected.
让我们看看以下实体:
Let’s have a look at the following entity:
class Person {
private final @Id Long id; 1
private final String firstname, lastname; 2
private final LocalDate birthday;
private final int age; 3
private String comment; 4
private @AccessType(Type.PROPERTY) String remarks; 5
static Person of(String firstname, String lastname, LocalDate birthday) { 6
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { 6
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) { 1
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) { 5
this.remarks = remarks;
}
}
1 | 标识符属性为最终值,但在构造函数中将其设置为 null 。类公开一个 withId(…) 方法,该方法用于设置标识符,例如,当将一个实例插入到数据存储中并且生成了一个标识符时。原始 Person 实例保持不变,因为创建了一个新的实例。通常将相同的模式应用于其他由存储管理但可能必须更改为持久化操作的属性。wither 方法是可选的,因为持久化构造函数(见 6)实际上是一个复制构造函数,并且设置属性将转换为创建一个应用了新标识符值的新实例。 |
2 | The identifier property is final but set to null in the constructor.
The class exposes a withId(…) method that’s used to set the identifier, e.g. when an instance is inserted into the datastore and an identifier has been generated.
The original Person instance stays unchanged as a new one is created.
The same pattern is usually applied for other properties that are store managed but might have to be changed for persistence operations.
The wither method is optional as the persistence constructor (see 6) is effectively a copy constructor and setting the property will be translated into creating a fresh instance with the new identifier value applied. |
3 | firstname 和 lastname 属性是可通过 Getter 公开的简单不可变属性。 |
4 | The firstname and lastname properties are ordinary immutable properties potentially exposed through getters. |
5 | age 属性是不可变但从 birthday 属性派生的属性。采用所示设计,数据库值将胜过默认值,这是因为 Spring Data 仅使用声明的构造函数。即使目的是计算优先,但此构造函数也必须将 age 作为参数(以潜在忽略它),否则属性填充步骤将尝试设置年龄字段并因为该字段不可变且没有存在 with… 方法而失败。 |
6 | The age property is an immutable but derived one from the birthday property.
With the design shown, the database value will trump the defaulting as Spring Data uses the only declared constructor.
Even if the intent is that the calculation should be preferred, it’s important that this constructor also takes age as parameter (to potentially ignore it) as otherwise the property population step will attempt to set the age field and fail due to it being immutable and no with… method being present. |
7 | comment 属性是可变的,通过直接设置其字段进行填充。 |
8 | The comment property is mutable and is populated by setting its field directly. |
9 | remarks 属性是可变的,通过调用赋值器方法进行填充。 |
10 | The remarks property is mutable and is populated by invoking the setter method. |
11 | 类公开了一个工厂方法和一个用于创建对象的构造函数。此处的核心概念是使用工厂方法,而不是附加构造函数,以避免必须通过 @PersistenceCreator 消除构造函数二义性。在工厂方法中处理属性的默认值。如果您想让 Spring Data 使用工厂方法实例化对象,请使用 @PersistenceCreator 为其添加注释。 |
12 | The class exposes a factory method and a constructor for object creation.
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through @PersistenceCreator .
Instead, defaulting of properties is handled within the factory method.
If you want Spring Data to use the factory method for object instantiation, annotate it with @PersistenceCreator . |
General recommendations
-
Try to stick to immutable objects — 不可变对象易于创建,因为具体化一个对象就是调用其构造函数。此外,这避免在您的领域对象中布满通过设置器方法允许客户端代码处理对象状态的情况。如果您需要这些代码,请使用程序包保护它们,以便只能通过有限的同置类型调用它们。仅构造函数具体化比属性填充快 30%。
-
Try to stick to immutable objects — Immutable objects are straightforward to create as materializing an object is then a matter of calling its constructor only. Also, this avoids your domain objects to be littered with setter methods that allow client code to manipulate the objects state. If you need those, prefer to make them package protected so that they can only be invoked by a limited amount of co-located types. Constructor-only materialization is up to 30% faster than properties population.
-
Provide an all-args constructor — 即使您不能或不想将实体建模为不可变值,提供一个构造函数(它将所有实体属性作为参数,包括可变属性)仍然有价值,因为这允许对象映射跳过属性填充,以获得最佳性能。
-
Provide an all-args constructor — Even if you cannot or don’t want to model your entities as immutable values, there’s still value in providing a constructor that takes all properties of the entity as arguments, including the mutable ones, as this allows the object mapping to skip the property population for optimal performance.
-
Use factory methods instead of overloaded constructors to avoid `@PersistenceCreator` — 为了获得最佳性能,通常需要一个所有参数构造函数,我们通常希望公开更多应用用例特定的构造函数,这些构造函数省略自动生成标识符等信息。一个成熟的模式是使用静态工厂方法公开所有参数构造函数的这些变体。
-
Use factory methods instead of overloaded constructors to avoid `@PersistenceCreator` — With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc. It’s an established pattern to rather use static factory methods to expose these variants of the all-args constructor.
-
Make sure you adhere to the constraints that allow the generated instantiator and property accessor classes to be used —
-
Make sure you adhere to the constraints that allow the generated instantiator and property accessor classes to be used —
-
For identifiers to be generated, still use a final field in combination with an all-arguments persistence constructor (preferred) or a
with…
method — -
For identifiers to be generated, still use a final field in combination with an all-arguments persistence constructor (preferred) or a
with…
method — -
Use Lombok to avoid boilerplate code — 由于持久化操作通常需要一个获取所有参数的构造函数,因此,它们的声明会变成枯燥地重复参数到字段赋值的样板,而使用 Lombok 的
@AllArgsConstructor
可以很好地避免这种情况。 -
Use Lombok to avoid boilerplate code — As persistence operations usually require a constructor taking all arguments, their declaration becomes a tedious repetition of boilerplate parameter to field assignments that can best be avoided by using Lombok’s
@AllArgsConstructor
.
Overriding Properties
Java 允许对领域类进行灵活的设计,其中子类可以定义在超类中已经使用相同名称声明的属性。考虑以下示例:
Java’s allows a flexible design of domain classes where a subclass could define a property that is already declared with the same name in its superclass. Consider the following example:
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);
}
}
这两个类都使用可分配的类型定义了 field
。但是,SubType
隐藏了 SuperType.field
。根据类设计,使用构造函数可能是设置 SuperType.field
的唯一默认方式。或者,在设置器中调用 super.setField(…)
可以设置 SuperType
中的 field
。由于这些属性共享相同名称,但可能表示两个不同的值,所有这些机制在某种程度上都会产生冲突。如果类型不可分配,Spring Data 将跳过超类型属性。也就是说,被重写的属性的类型必须可以分配给其超类型属性类型才能被注册为覆盖项,否则超类型属性被视为瞬态。我们通常建议使用不同的属性名称。
Both classes define a field
using assignable types. SubType
however shadows SuperType.field
.
Depending on the class design, using the constructor could be the only default approach to set SuperType.field
.
Alternatively, calling super.setField(…)
in the setter could set the field
in SuperType
.
All these mechanisms create conflicts to some degree because the properties share the same name yet might represent two distinct values.
Spring Data skips super-type properties if types are not assignable.
That is, the type of the overridden property must be assignable to its super-type property type to be registered as override, otherwise the super-type property is considered transient.
We generally recommend using distinct property names.
Spring Data 模块普遍支持包含不同值的重写属性。从编程模型的角度来看,有几件事需要考虑:
Spring Data modules generally support overridden properties holding different values. From a programming model perspective there are a few things to consider:
-
哪种属性应该持久化(默认为所有已声明的属性)?您可以添加
@Transient
注释到属性上以排除它们。 -
Which property should be persisted (default to all declared properties)? You can exclude properties by annotating these with
@Transient
. -
如何在数据存储中表示属性?不同值使用相同的字段/列名称通常会导致数据损坏,因此您应该至少使用一个显式字段/列名称为属性添加注释。
-
How to represent properties in your data store? Using the same field/column name for different values typically leads to corrupt data so you should annotate least one of the properties using an explicit field/column name.
-
由于不能在不针对赋值器实现做出进一步假设的情况下设置超属性,所以不能使用
@AccessType(PROPERTY)
。 -
Using
@AccessType(PROPERTY)
cannot be used as the super-property cannot be generally set without making any further assumptions of the setter implementation.
Kotlin support
Spring Data 适应 Kotlin 的特殊性,允许创建和更改对象。
Spring Data adapts specifics of Kotlin to allow object creation and mutation.
Kotlin object creation
支持实例化 Kotlin 类,所有类在默认情况下都是不可变的,需要显式属性声明才能定义可变属性。
Kotlin classes are supported to be instantiated, all classes are immutable by default and require explicit property declarations to define mutable properties.
Spring Data 自动尝试检测一个持久实体的构造函数,以便用于实现该类型的对象。解决算法的工作方式如下:
Spring Data automatically tries to detect a persistent entity’s constructor to be used to materialize objects of that type. The resolution algorithm works as follows:
-
如果有一个带
@PersistenceCreator
注释的构造函数,则使用它。 -
If there is a constructor that is annotated with
@PersistenceCreator
, it is used. -
如果类型是 Kotlin data cass,则使用主构造函数。
-
If the type is a mapping.kotlin the primary constructor is used.
-
如果有使用
@PersistenceCreator
注释的单个静态工厂方法,则使用该方法。 -
If there is a single static factory method annotated with
@PersistenceCreator
then it is used. -
如果有单个构造函数,则使用该构造函数。
-
If there is a single constructor, it is used.
-
如果有多个构造函数,并且只有一个使用
@PersistenceCreator
注释,则使用该构造函数。 -
If there are multiple constructors and exactly one is annotated with
@PersistenceCreator
, it is used. -
如果该类型是一个 Java
Record
,则使用规范构造函数。 -
If the type is a Java
Record
the canonical constructor is used. -
如果有无参数构造函数,则会使用它。将忽略其他构造函数。
-
If there’s a no-argument constructor, it is used. Other constructors will be ignored.
考虑以下 data
类 Person
:
Consider the following data
class Person
:
data class Person(val id: String, val name: String)
上述类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数并用 @PersistenceCreator
注释它来定制此类,以指示构造函数偏好:
The class above compiles to a typical class with an explicit constructor.We can customize this class by adding another constructor and annotate it with @PersistenceCreator
to indicate a constructor preference:
data class Person(var id: String, val name: String) {
@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}
Kotlin 支持参数可选择性,允许在不提供参数时使用默认值。当 Spring Data 检测到带有参数默认值的构造函数时,它会让这些参数保持缺失状态(或仅返回 null
),以便 Kotlin 可以应用参数默认值。考虑以下对 name
应用参数默认值的类
Kotlin supports parameter optionality by allowing default values to be used if a parameter is not provided.
When Spring Data detects a constructor with parameter defaulting, then it leaves these parameters absent if the data store does not provide a value (or simply returns null
) so Kotlin can apply parameter defaulting.Consider the following class that applies parameter defaulting for name
data class Person(var id: String, val name: String = "unknown")
每次当 name
参数既不属于结果的一部分或其值为 null
,则 name
将默认为 unknown
。
Every time the name
parameter is either not part of the result or its value is null
, then the name
defaults to unknown
.
Property population of Kotlin data classes
在 Kotlin 中,所有类默认情况下是不可变的,需要显式声明属性来定义可变属性。考虑下面的 data
类 Person
:
In Kotlin, all classes are immutable by default and require explicit property declarations to define mutable properties.
Consider the following data
class Person
:
data class Person(val id: String, val name: String)
这个类实际上是不可变的。它允许创建新的实例,因为 Kotlin 生成了一个 copy(…)
方法,该方法创建新的对象实例,复制所有属性值(从现有对象)并将作为参数提供给该方法的属性值应用进来。
This class is effectively immutable.
It allows creating new instances as Kotlin generates a copy(…)
method that creates new object instances copying all property values from the existing object and applying property values provided as arguments to the method.
Kotlin Overriding Properties
Kotlin 允许声明 `` 来更改子类中的属性。
Kotlin allows declaring property overrides to alter properties in subclasses.
open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
这种安排使两个名称为 field
的属性得以呈现。Kotlin 中为每个类中每个属性生成了属性访问器(getter 和 setter)。实际上,该代码如下所示:
Such an arrangement renders two properties with the name field
.
Kotlin generates property accessors (getters and setters) for each property in each class.
Effectively, the code looks like as follows:
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;
}
}
SubType
上的 getter 和 setter 仅设置 SubType.field
而不设置 SuperType.field
。在这样的安排中,使用构造函数是设置 SuperType.field
的唯一默认方法。向 SubType
中添加一个方法以通过 this.SuperType.field = …
来设置 SuperType.field
是可能的,但是不属于受支持的约定。属性覆盖在某种程度上创建了冲突,因为这些属性共享相同名称,但可能表示两个不同的值。我们通常建议使用不同的属性名称。
Getters and setters on SubType
set only SubType.field
and not SuperType.field
.
In such an arrangement, using the constructor is the only default approach to set SuperType.field
.
Adding a method to SubType
to set SuperType.field
via this.SuperType.field = …
is possible but falls outside of supported conventions.
Property overrides create conflicts to some degree because the properties share the same name yet might represent two distinct values.
We generally recommend using distinct property names.
Spring Data 模块普遍支持包含不同值的重写属性。从编程模型的角度来看,有几件事需要考虑:
Spring Data modules generally support overridden properties holding different values. From a programming model perspective there are a few things to consider:
-
哪种属性应该持久化(默认为所有已声明的属性)?您可以添加
@Transient
注释到属性上以排除它们。 -
Which property should be persisted (default to all declared properties)? You can exclude properties by annotating these with
@Transient
. -
如何在数据存储中表示属性?不同值使用相同的字段/列名称通常会导致数据损坏,因此您应该至少使用一个显式字段/列名称为属性添加注释。
-
How to represent properties in your data store? Using the same field/column name for different values typically leads to corrupt data so you should annotate least one of the properties using an explicit field/column name.
-
由于不能设置超属性,所以不能使用
@AccessType(PROPERTY)
。 -
Using
@AccessType(PROPERTY)
cannot be used as the super-property cannot be set.
Kotlin Value Classes
Kotlin 值类被设计用于更具表现力的领域模型来使基础概念明确化。Spring Data 可以读取和写入使用值类定义属性的类型。
Kotlin Value Classes are designed for a more expressive domain model to make underlying concepts explicit. Spring Data can read and write types that define properties using Value Classes.
考虑以下领域模型:
Consider the following domain model:
@JvmInline
value class EmailAddress(val theAddress: String) 1
data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) 2
1 | 具有非空值类型的简单值类。 |
2 | A simple value class with a non-nullable value type. |
3 | 数据类使用 EmailAddress 值类定义属性。 |
4 | Data class defining a property using the EmailAddress value class. |
在已编译类中,使用非基本值类型而非空属性会展平为该值类型。可空基本值类型或可空值中值类型将用包装器类型表示,并影响在数据库中如何表示值类型。 |
Non-nullable properties using non-primitive value types are flattened in the compiled class to the value type. Nullable primitive value types or nullable value-in-value types are represented with their wrapper type and that affects how value types are represented in the database. |