Spring Data (数据)MongoDB(四)

时间:2022-11-21 19:00:27

Spring Data (数据)MongoDB(四)

17. 映射

丰富的映射支持由.have提供,具有丰富的元数据模型,该模型提供了将域对象映射到MongoDB文档的完整功能集。 映射元数据模型通过使用域对象上的注释进行填充。 但是,基础结构不仅限于使用注释作为元数据信息的唯一来源。 它还允许您通过遵循一组约定将对象映射到文档,而无需提供任何其他元数据。​​MappingMongoConverter​​​​MappingMongoConverter​​​​MappingMongoConverter​

本节介绍的功能,包括基础知识,如何使用约定将对象映射到文档,以及如何使用基于批注的映射元数据覆盖这些约定。​​MappingMongoConverter​

17.1. 对象映射基础

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

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

  1. 使用公开的构造函数之一创建实例。
  2. 实例填充以具体化所有公开的属性。

17.1.1. 对象创建

Spring Data 自动尝试检测持久实体的构造函数,以用于具体化该类型的对象。 解析算法的工作原理如下:

  1. 如果有一个静态工厂方法注释,则使用它。@PersistenceCreator
  2. 如果只有一个构造函数,则使用它。
  3. 如果有多个构造函数,并且只有一个被批注,则使用它。@PersistenceCreator
  4. 如果类型是 Java,则使用规范构造函数。Record
  5. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。

值解析假定构造函数/工厂方法参数名称与实体的属性名称匹配,即解析将像要填充属性一样执行,包括映射中的所有自定义(不同的数据存储列或字段名称等)。 这还需要类文件中可用的参数名称信息或构造函数上存在的枚举注释。​​@ConstructorProperties​

值解析可以通过使用特定于商店的SpEL表达式使用Spring Framework的值注释来自定义。 有关更多详细信息,请参阅商店特定映射部分。​​@Value​

对象创建内部

为了避免反射的开销,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 数据将通过反射回退到实体实例化。

17.1.2. 财产人口

创建实体的实例后,Spring Data 将填充该类的所有剩余持久属性。 除非已由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。 之后,将在实体实例上设置构造函数尚未填充的所有非瞬态属性。 为此,我们使用以下算法:

  1. 如果属性是不可变的,但公开了 amethod(见下文),我们使用该方法创建一个具有新属性值的新实体实例。with…with…
  2. 如果定义了属性访问(即通过 getter 和 setter 的访问),我们将调用 setter 方法。
  3. 如果属性是可变的,我们直接设置字段。
  4. 如果属性是不可变的,我们将使用持久性操作要使用的构造函数(请参阅对象创建)来创建实例的副本。
  5. 默认情况下,我们直接设置字段值。

属性人口内部

与对象构造中的优化类似,我们还使用 Spring 数据运行时生成的访问器类与实体实例进行交互。

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;
}
}

例 174.生成的属性访问器

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 数据使用字段访问来读取和写入属性值。根据字段的可见性规则,用于与字段进行交互。​​private​​​​MethodHandles​

该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。调用创建一个新对象。所有后续突变都将发生在新实例中,而之前的突变保持不变。​​withId(…)​​​​withId(…)​​​​Person​

使用属性访问允许直接调用方法,而无需使用。​​MethodHandles​

这为我们提供了大约 25% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:

  • 类型不得驻留在默认值或包下。java
  • 类型及其构造函数必须是public
  • 内部类的类型必须是。static
  • 使用的 Java 运行时必须允许在原始文件中声明类。Java 9 及更高版本施加了某些限制。ClassLoader

默认情况下,Spring Data 尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的属性访问器。

让我们看一下以下实体:

例 175.示例实体

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;
}
}


标识符属性是最终的,但在构造函数中设置为。 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。 创建新实例时,原始实例保持不变。 相同的模式通常应用于存储管理的其他属性,但可能必须更改持久性操作。 wither 方法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,设置属性将转换为创建应用了新标识符值的新实例。​​null​​​​withId(…)​​​​Person​


和属性是普通的不可变属性,可能通过 getter 公开。​​firstname​​​​lastname​


属性是不可变的,但派生自属性。 按照所示的设计,数据库值将胜过默认值,因为 Spring Data 使用唯一声明的构造函数。 即使意图是首选计算,重要的是此构造函数也采用 as 参数(可能忽略它),否则属性填充步骤将尝试设置 age 字段并失败,因为它是不可变的并且不存在任何方法。​​age​​​​birthday​​​​age​​​​with…​


属性是可变的,通过直接设置其字段来填充。​​comment​


属性是可变的,并通过直接设置 thefield 或通过调用 setter 方法来填充​​remarks​​​​comment​


该类公开用于创建对象的工厂方法和构造函数。 这里的核心思想是使用工厂方法而不是其他构造函数,以避免通过构造函数消除歧义的需要。 相反,属性的默认值在工厂方法中处理。 如果您希望 Spring Data 使用工厂方法进行对象实例化,请使用 注释。​​@PersistenceCreator​​​​@PersistenceCreator​

17.1.3. 一般建议

  • 尝试坚持使用不可变对象 - 不可变对象很容易创建,因为具体化对象只需调用其构造函数即可。 此外,这可以避免域对象充斥着允许客户端代码操作对象状态的 setter 方法。 如果需要这些,最好使它们受到包保护,以便只能由有限数量的共存类型调用它们。 仅构造函数具体化比属性填充快 30%。
  • 提供全参数构造函数 — 即使不能或不想将实体建模为不可变值,提供将实体的所有属性(包括可变属性)作为参数的构造函数仍然有价值,因为这允许对象映射跳过属性填充以获得最佳性能。
  • 使用工厂方法而不是重载的构造函数来避免​​@PersistenceCreator​​ — 使用最佳性能所需的全参数构造函数,我们通常希望公开更多特定于应用程序用例的构造函数,这些构造函数省略了自动生成的标识符等内容。 这是一种既定模式,而是使用静态工厂方法来公开 all-args 构造函数的这些变体。
  • 确保遵守允许使用生成的实例化器和属性访问器类的约束
  • 对于要生成的标识符,仍将最终字段与全参数持久性构造函数(首选)或​​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);
}
}

这两个类都使用可赋值类型定义。 根据类设计,使用构造函数可能是唯一的默认设置方法。 或者,调用二传手可以设置。 所有这些机制在某种程度上都会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 如果类型不可分配,则 Spring Data 将跳过超类型属性。 也就是说,重写属性的类型必须可分配给其超类型属性类型才能注册为重写,否则超类型属性被视为暂时性属性。 我们通常建议使用不同的属性名称。​​field​​​​SubType​​​​SuperType.field​​​​SuperType.field​​​​super.setField(…)​​​​field​​​​SuperType​

Spring 数据模块通常支持保存不同值的被覆盖属性。 从编程模型的角度来看,需要考虑以下几点:

  1. 应保留哪个属性(默认为所有声明的属性)? 您可以通过用这些属性批注来排除属性。@Transient
  2. 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少对其中一个属性进行批注。
  3. 不能使用,因为如果不对 setter 实现进行任何进一步的假设,通常无法设置超级属性。@AccessType(PROPERTY)

17.1.4. Kotlin 支持

Spring Data 调整了 Kotlin 的细节,以允许对象创建和更改。

Kotlin 对象创建

Kotlin 类支持实例化,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。

Spring Data 自动尝试检测持久实体的构造函数,以用于具体化该类型的对象。 解析算法的工作原理如下:

  1. 如果存在带有注释的构造函数,则使用它。@PersistenceCreator
  2. 如果类型是Kotlin 数据 cass,则使用主构造函数。
  3. 如果有一个静态工厂方法注释,则使用它。@PersistenceCreator
  4. 如果只有一个构造函数,则使用它。
  5. 如果有多个构造函数,并且只有一个被批注,则使用它。@PersistenceCreator
  6. 如果类型是 Java,则使用规范构造函数。Record
  7. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。

请考虑以下类:​​data​​​​Person​

data class Person(val id: String, val name: String)

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义此类并对其进行注释以指示构造函数首选项:​​@PersistenceCreator​

data class Person(var id: String, val name: String) {

@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}

Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。 当 Spring Data 检测到参数默认值的构造函数时,如果数据存储不提供值(或只是返回),则这些参数将保留为不存在,以便 Kotlin 可以应用参数默认值。请考虑以下应用参数默认值的类​​null​​​​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) {
}

这种安排呈现两个具有名称的属性。 Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。 实际上,代码如下所示:​​field​

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;
}
}

吉特和二传手仅发病,不发病。 在这种安排中,使用构造函数是唯一要设置的默认方法。 可以添加方法toto setvia,但不属于支持的约定。 属性重写在某种程度上会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 我们通常建议使用不同的属性名称。​​SubType​​​​SubType.field​​​​SuperType.field​​​​SuperType.field​​​​SubType​​​​SuperType.field​​​​this.SuperType.field = …​

Spring 数据模块通常支持保存不同值的被覆盖属性。 从编程模型的角度来看,需要考虑以下几点:

  1. 应保留哪个属性(默认为所有声明的属性)? 您可以通过用这些属性批注来排除属性。@Transient
  2. 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少对其中一个属性进行批注。
  3. Using cannot be used as the super-property cannot be set.@AccessType(PROPERTY)

17.2. 基于约定的映射

​MappingMongoConverter​​具有一些约定,用于在未提供其他映射元数据时将对象映射到文档。这些约定是:

  • 短 Java 类名按以下方式映射到集合名。类映射到集合名称。com.bigbank.SavingsAccountsavingsAccount
  • 所有嵌套对象都作为嵌套对象存储在文档中,而不是作为 DBRef。
  • 转换器使用向其注册的任何 Spring 转换器来覆盖对象属性到文档字段和值的默认映射。
  • 对象的字段用于与文档中的字段进行转换。不使用公共属性。JavaBean
  • 如果有一个非零参数构造函数,其构造函数参数名称与文档的*字段名称匹配,则使用该构造函数。否则,将使用零参数构造函数。如果有多个非零参数构造函数,则会引发异常。

17.2.1. 如何在映射层中处理字段。​​_id​

MongoDB要求你对所有文档都有anfield。如果未提供,驱动程序将为 ObjectId 分配生成的值。“_id”字段可以是数组以外的任何类型的字段,只要它是唯一的。驱动程序自然支持所有基元类型和日期。使用时,有一些规则控制如何将 Java 类的属性映射到此字段。​​_id​​​​MappingMongoConverter​​​​_id​

下面概述了将映射到文档字段的字段:​​_id​

  • 用 () 注释的字段将映射到该字段。@Idorg.springframework.data.annotation.Id_id
  • 没有注释但已命名的字段将映射到该字段。id_id
  • 标识符的默认字段名称是 可以通过注释进行自定义。_id@Field

表 10.字段定义翻译示例​​_id​

字段定义

在 MongoDB 中生成的 id-字段名

​String​​编号

​_id​

​@Field​​​ ​​String​​编号

​_id​

​@Field("x")​​​ ​​String​​编号

​x​

​@Id​​​ ​​String​​ x

​_id​

​@Field("x")​​​ ​​@Id​​​ ​​String​​ x

​_id​

下面概述了将对映射到_id文档字段的属性执行的类型转换(如果有)。

  • 如果字段 namedis 在 Java 类中声明为 String 或 BigInteger,则如果可能,它将被转换为 ObjectId 并存储为对象 Id。作为字段类型的 ObjectId 也是有效的。如果在应用程序中指定值,则会检测到 MongoDB 驱动程序对 ObjectId 的转换。如果指定的值无法转换为 ObjectId,则该值将按原样存储在文档的_id字段中。如果字段带有注释,这也适用。ididid@Id
  • 如果在 Java 类中对字段进行了注释,则该字段将被转换为使用其实际类型进行存储。除非声明所需的字段类型,否则不会发生进一步的转换。如果未为字段提供值,则将创建一个 new,并将其转换为属性类型。@MongoId@MongoIdidObjectId
  • 如果在 Java 类中注释了字段,则将尝试将值转换为声明的值。如果没有为 该字段提供值,则会创建一个 newwill 并将其转换为声明的类型。@MongoId(FieldType.…)FieldTypeidObjectId
  • 如果字段 namedid 字段未在 Java 类中声明为 String、BigInteger 或 ObjectID,则应在应用程序中为其赋值,以便它可以“按原样”存储在文档的_id字段中。id
  • 如果 Java 类中不存在字段 namedis,则驱动程序将生成一个隐式文件,但不会映射到 Java 类的属性或字段。id_id

查询和更新时将使用转换器来处理与上述保存文档规则相对应的 theandobjects 的转换,以便查询中使用的字段名称和类型能够匹配域类中的内容。​​MongoTemplate​​​​Query​​​​Update​

17.3. 数据映射和类型转换

本节介绍如何将类型映射到 MongoDB 表示形式以及从 MongoDB 表示映射类型。Spring Data MongoDB支持所有可以表示为BSON的类型,BSON是MongoDB的内部文档格式。 除了这些类型之外,Spring Data MongoDB还提供了一组内置转换器来映射其他类型。您可以提供自己的转换器来调整类型转换。有关更多详细信息,请参阅 [映射显式转换器]。

下面提供了每个可用类型转换的示例:

表 11.类型

类型

类型转换

样本

​String​

本地

​{"firstname" : "Dave"}​

​double​​​, , , ​​Double​​​​float​​​​Float​

本地

​{"weight" : 42.5}​

​int​​​, , , ​​Integer​​​​short​​​​Short​

本机
32 位整数

​{"height" : 42}​

​long​​​, ​​Long​

本机
64 位整数

​{"height" : 42}​

​Date​​​, ​​Timestamp​

本地

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​byte[]​

本地

​{"bin" : { "$binary" : "AQIDBA==", "$type" : "00" }}​

​java.util.UUID​​(旧版 UUID)

本地

​{"uuid" : { "$binary" : "MEaf1CFQ6lSphaa3b9AtlA==", "$type" : "03" }}​

​Date​

本地

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​ObjectId​

本地

​{"_id" : ObjectId("5707a2690364aba3136ab870")}​

数组​​List​​​​BasicDBList​

本地

​{"cookies" : [ … ]}​

​boolean​​​, ​​Boolean​

本地

​{"active" : true}​

​null​

本地

​{"value" : null}​

​Document​

本地

​{"value" : { … }}​

​Decimal128​

本地

​{"value" : NumberDecimal(…)}​

​AtomicInteger​​调用在实际转换之前​​get()​

转换器
32 位整数

​{"value" : "741" }​

​AtomicLong​​调用在实际转换之前​​get()​

转换器
64 位整数

​{"value" : "741" }​

​BigInteger​

转炉
​​​String​

​{"value" : "741" }​

​BigDecimal​

转炉
​​​String​

​{"value" : "741.99" }​

​URL​

转炉

​{"website" : "https://spring.io/projects/spring-data-mongodb/" }​

​Locale​

转炉

​{"locale : "en_US" }​

​char​​​, ​​Character​

转炉

​{"char" : "a" }​

​NamedMongoScript​

转炉
​​​Code​

​{"_id" : "script name", value: (some javascript code)​​}

​java.util.Currency​

转炉

​{"currencyCode" : "EUR"}​

​Instant​​(爪哇 8)

本地

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​Instant​​(Joda, JSR310-BackPort)

转炉

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​LocalDate​​(Joda, Java 8, JSR310-BackPort)

转换器/原生(Java8)[​​2​​]

​{"date" : ISODate("2019-11-12T00:00:00.000Z")}​

​LocalDateTime​​​,
(Joda, Java 8, JSR310-BackPort)​​​LocalTime​

转换器/原生(Java8)[​​3​​]

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​DateTime​​(乔达)

转炉

​{"date" : ISODate("2019-11-12T23:00:00.809Z")}​

​ZoneId​​(Java 8, JSR310-BackPort)

转炉

​{"zoneId" : "ECT - Europe/Paris"}​

​Box​

转炉

​{"box" : { "first" : { "x" : 1.0 , "y" : 2.0} , "second" : { "x" : 3.0 , "y" : 4.0}}​

​Polygon​

转炉

​{"polygon" : { "points" : [ { "x" : 1.0 , "y" : 2.0} , { "x" : 3.0 , "y" : 4.0} , { "x" : 4.0 , "y" : 5.0}]}}​

​Circle​

转炉

​{"circle" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}​

​Point​

转炉

​{"point" : { "x" : 1.0 , "y" : 2.0}}​

​GeoJsonPoint​

转炉

​{"point" : { "type" : "Point" , "coordinates" : [3.0 , 4.0] }}​

​GeoJsonMultiPoint​

转炉

​{"geoJsonLineString" : {"type":"MultiPoint", "coordinates": [ [ 0 , 0 ], [ 0 , 1 ], [ 1 , 1 ] ] }}​

​Sphere​

转炉

​{"sphere" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}​

​GeoJsonPolygon​

转炉

​{"polygon" : { "type" : "Polygon", "coordinates" : [[ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 1 ], [ 0 , 0 ] ]] }}​

​GeoJsonMultiPolygon​

转炉

​{"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [[ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ],[ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ]] }}​

​GeoJsonLineString​

转炉

​{ "geoJsonLineString" : { "type" : "LineString", "coordinates" : [ [ 40 , 5 ], [ 41 , 6 ] ] }}​

​GeoJsonMultiLineString​

转炉

​{"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [[ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ],[ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ]] }}​

17.4. 映射配置

除非显式配置,否则在创建 时默认创建 的实例。您可以创建自己的实例。这样做可以让你指定在类路径中可以找到你的域类的位置,以便Spring Data MongoDB可以提取元数据并构造索引。此外,通过创建自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库和从数据库映射特定类。​​MappingMongoConverter​​​​MongoTemplate​​​​MappingMongoConverter​

您可以使用基于 Java 或基于 XML 的元数据来配置和 MongoTemplate。以下示例显示了配置:​​MappingMongoConverter​​​​com.mongodb.client.MongoClient​

爪哇岛

.XML

@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

@Override
public String getDatabaseName() {
return "database";
}

// the following are optional

@Override
public String getMappingBasePackage() {
return "com.bigbank.domain";
}

@Override
void configureConverters(MongoConverterConfigurationAdapter adapter) {

adapter.registerConverter(new org.springframework.data.mongodb.test.PersonReadConverter());
adapter.registerConverter(new org.springframework.data.mongodb.test.PersonWriteConverter());
}

@Bean
public LoggingEventListener<MongoMappingEvent> mappingEventsListener() {
return new LoggingEventListener<MongoMappingEvent>();
}
}

映射基础包定义用于扫描用于预初始化的实体的根路径。缺省情况下,使用配置类包。​​MappingContext​

为特定域类型配置其他自定义转换器,以将这些类型的默认映射过程替换为自定义实现。

​AbstractMongoClientConfiguration​​要求您实现定义 aas 的方法并提供数据库名称。还有一个名为 Certified 的方法,您可以重写该方法以告知转换器在何处扫描使用注释的类。​​com.mongodb.client.MongoClient​​​​AbstractMongoClientConfiguration​​​​getMappingBasePackage(…)​​​​@Document​

您可以通过重写方法向转换器添加其他转换器。 MongoDB的原生JSR-310支持可以通过。 前面的例子中还显示了 a,它记录了发布到 Spring 基础设施上的实例。​​customConversionsConfiguration​​​​MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()​​​​LoggingEventListener​​​​MongoMappingEvent​​​​ApplicationContextEvent​


​AbstractMongoClientConfiguration​​​创建一个实例,并将其注册到名称下的容器中。​​MongoTemplate​​​​mongoTemplate​

该属性告诉它在哪里扫描用注释注释的类。​​base-package​​​​@org.springframework.data.mongodb.core.mapping.Document​

17.5. 基于元数据的映射

为了充分利用Spring Data MongoDB支持中的对象映射功能,您应该使用注释来注释映射的对象。 尽管映射框架不必具有此注释(即使没有任何注释,您的 POJO 也已正确映射),但它允许类路径扫描程序查找并预处理域对象以提取必要的元数据。 如果不使用此注释,则应用程序在首次存储域对象时性能会略有下降,因为映射框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何持久化它们。 以下示例显示了一个域对象:​​@Document​

例 176.示例域对象

package com.mycompany.domain;

@Document
public class Person {

@Id
private ObjectId id;

@Indexed
private Integer ssn;

private String firstName;

@Indexed
private String lastName;
}

Theannotation告诉映射器你想将哪个属性用于MongoDBproperty,而theannotation告诉映射框架调用你的文档的该属性,从而使搜索更快。 仅对带有批注的类型执行自动索引创建。​​@Id​​​​_id​​​​@Indexed​​​​createIndex(…)​​​​@Document​

默认情况下,自动索引创建处于禁用状态,需要通过配置启用(请参阅索引创建)。

17.5.1. 索引创建

Spring Data MongoDB可以自动为带有注释的实体类型创建索引。 必须从版本 3.0 开始显式启用索引创建,以防止对集合生命周期和性能造成意外影响。 在应用程序启动时以及在应用程序运行时首次访问实体类型时,会自动为初始实体集创建索引。​​@Document​

我们通常建议显式创建索引以进行基于应用程序的索引控制,因为 Spring Data 无法自动为在应用程序运行时重新创建的集合创建索引。

​IndexResolver​​提供用于创建编程索引定义(如果要使用注释(如、、and)的抽象。 可以使用索引定义来创建索引。 创建索引的一个好时间点是在应用程序启动时,特别是在应用程序上下文刷新后,由观察触发。 此事件保证上下文已完全初始化。 请注意,此时其他组件,尤其是 Bean 工厂可能有权访问 MongoDB 数据库。​​@Indexed​​​​@GeoSpatialIndexed​​​​@TextIndexed​​​​@CompoundIndex​​​​@WildcardIndexed​​​​IndexOperations​​​​ContextRefreshedEvent​


​Map​​-like 属性被跳过,除非注释为 因为映射键必须是索引定义的一部分。由于映射的目的是使用动态键和值,因此无法从静态映射元数据解析键。​​IndexResolver​​​​@WildcardIndexed​


例 177.为单个域类型创建编程索引

class MyListener {

@EventListener(ContextRefreshedEvent.class)
public void initIndicesAfterStartup() {

MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate
.getConverter().getMappingContext();

IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext);

IndexOperations indexOps = mongoTemplate.indexOps(DomainType.class);
resolver.resolveIndexFor(DomainType.class).forEach(indexOps::ensureIndex);
}
}

例 178.为所有初始实体创建编程索引

class MyListener{

@EventListener(ContextRefreshedEvent.class)
public void initIndicesAfterStartup() {

MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate
.getConverter().getMappingContext();

// consider only entities that are annotated with @Document
mappingContext.getPersistentEntities()
.stream()
.filter(it -> it.isAnnotationPresent(Document.class))
.forEach(it -> {

IndexOperations indexOps = mongoTemplate.indexOps(it.getType());
resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex);
});
}
}

或者,如果要在任何组件能够从应用程序访问数据库之前确保索引和集合存在,请在返回对象之前声明 amethod,并包含上面的代码。​​@Bean​​​​MongoTemplate​​​​MongoTemplate​


要打开自动索引创建,请覆盖您的配置。​​autoIndexCreation()​




@Configuration
public class Config extends AbstractMongoClientConfiguration {

@Override
public boolean autoIndexCreation() {
return true;
}

// ...
}



从版本 3.0 开始,默认情况下自动索引创建处于关闭状态。

17.5.2. 映射注释概述

MappingMongoConverter可以使用元数据来驱动对象到文档的映射。以下注释可用:

  • ​@Id​​:在字段级别应用,以标记用于标识目的的字段。
  • ​@MongoId​​:在字段级别应用,以标记用于标识目的的字段。接受可选的自定义 id 转换。FieldType
  • ​@Document​​:在类级别应用,以指示此类是映射到数据库的候选项。可以指定将存储数据的集合的名称。
  • ​@DBRef​​:应用于字段以指示将使用com.mongodb.DBRef存储它。
  • ​@DocumentReference​​:应用于字段以指示要存储为指向另一个文档的指针。这可以是单个值(默认为id),也可以是通过转换器提供的。Document
  • ​@Indexed​​:应用于字段级别,用于描述如何为字段编制索引。
  • ​@CompoundIndex​​(可重复):在类型级别应用于声明复合索引。
  • ​@GeoSpatialIndexed​​:应用于外业级别,用于描述如何对外业进行地理索引。
  • ​@TextIndexed​​:在字段级别应用以标记要包含在文本索引中的字段。
  • ​@HashIndexed​​:在字段级别应用,以便在哈希索引中使用,以跨分片集群对数据进行分区。
  • ​@Language​​:在字段级别应用以设置文本索引的语言覆盖属性。
  • ​@Transient​​:默认情况下,所有字段都映射到文档。此批注将应用该批注的字段排除在数据库中的存储之外。不能在持久性构造函数中使用瞬态属性,因为转换器无法具体化构造函数参数的值。
  • ​@PersistenceConstructor​​:标记给定的构造函数 - 甚至是受包保护的构造函数 - 以便在从数据库中实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。
  • ​@Value​​:此注释是 Spring 框架的一部分。在映射框架内,它可以应用于构造函数参数。这允许您使用 Spring 表达式语言语句来转换数据库中检索到的键值,然后再将其用于构造域对象。为了引用给定文档的属性,必须使用诸如:where引用给定文档的根的表达式。@Value("#root.myProperty")root
  • ​@Field​​:应用于字段级别,它允许描述字段的名称和类型,因为它将在MongoDB BSON文档中表示,从而允许名称和类型与类的字段名称以及属性类型不同。
  • ​@Version​​:在字段级别应用用于乐观锁定,并检查保存操作的修改。初始值是(对于基元类型),每次更新时都会自动增加。zeroone

映射元数据基础设施在一个单独的 spring-data-commons 项目中定义,该项目与技术无关。MongoDB支持中使用的特定子类来支持基于注释的元数据。如果有需求,也可以采取其他策略。

下面是一个更复杂的映射示例。

@Document
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
public class Person<T extends Address> {

@Id
private String id;

@Indexed(unique = true)
private Integer ssn;

@Field("fName")
private String firstName;

@Indexed
private String lastName;

private Integer age;

@Transient
private Integer accountTotal;

@DBRef
private List<Account> accounts;

private T address;

public Person(Integer ssn) {
this.ssn = ssn;
}

@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}

public String getId() {
return id;
}

// no setter for Id. (getter is only exposed for some unit testing)

public Integer getSsn() {
return ssn;
}

// other getters/setters omitted
}



​@Field(targetType=…)​​​当映射基础设施推断的本机MongoDB类型不时,可以派上用场 匹配预期的。喜欢,表示为代替,只是因为更早 MongoDB Server版本不支持它。​​BigDecimal​​​​String​​​​Decimal128​




public class Balance {

@Field(targetType = DECIMAL128)
private BigDecimal value;

// ...
}




您甚至可以考虑自己的自定义注释。




@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Field(targetType = FieldType.DECIMAL128)
public @interface Decimal128 { }

// ...

public class Balance {

@Decimal128
private BigDecimal value;

// ...
}



17.5.3. 自定义对象构造

映射子系统允许通过使用注释注释构造函数来自定义对象构造。要用于构造函数参数的值按以下方式解析:​​@PersistenceConstructor​

  • 如果使用注释注释参数,则计算给定表达式并将结果用作参数值。@Value
  • 如果 Java 类型的属性名称与输入文档的给定字段匹配,则其属性信息用于选择要将输入字段值传递到的相应构造函数参数。仅当 java 文件中存在参数名称信息时,这才有效,这可以通过使用调试信息编译源代码或使用 Java 8 中 javac 的新命令行开关来实现。.class-parameters
  • 否则,将抛出 a,指示无法绑定给定的构造函数参数。MappingException
class OrderItem {

private @Id String id;
private int quantity;
private double unitPrice;

OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) {
this.id = id;
this.quantity = quantity;
this.unitPrice = unitPrice;
}

// getters/setters ommitted
}

Document input = new Document("id", "4711");
input.put("unitPrice", 2.5);
input.put("qty",5);
OrderItem item = converter.read(OrderItem.class, input);

参数注释中的 SpEL 表达式回退到值如果无法解析给定的属性路径。​​@Value​​​​quantity​​​​0​

有关使用注释的其他示例可以在MappingMongoConverterUnitTests测试套件中找到。​​@PersistenceConstructor​

17.5.4. 复合索引

还支持复合索引。它们是在类级别定义的,而不是在单个属性上定义的。

复合索引对于提高涉及多个字段条件的查询的性能非常重要

下面是一个示例,用于创建按升序和降序排列的复合索引:​​lastName​​​​age​

例 179.复合索引用法示例

package com.mycompany.domain;

@Document
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
public class Person {

@Id
private ObjectId id;
private Integer age;
private String firstName;
private String lastName;

}


​@CompoundIndex​​​可重复使用作为其容器。​​@CompoundIndexes​




@Document
@CompoundIndex(name = "cmp-idx-one", def = "{'firstname': 1, 'lastname': -1}")
@CompoundIndex(name = "cmp-idx-two", def = "{'address.city': -1, 'address.street': 1}")
public class Person {

String firstname;
String lastname;

Address address;

// ...
}



17.5.5. 散列索引

哈希索引允许在分片集群中进行基于哈希的分片。 使用哈希字段值对集合进行分片会导致更随机的分布。 有关详细信息,请参阅​​MongoDB 文档​​。

下面是一个为其创建哈希索引的示例:​​_id​

例 180。哈希索引用法示例

@Document
public class DomainType {

@HashIndexed @Id String id;

// ...
}

可以在其他索引定义旁边创建散列索引,如下所示,在这种情况下,将创建两个索引:

例 181.哈希索引用法示例以及简单索引

@Document
public class DomainType {

@Indexed
@HashIndexed
String value;

// ...
}

如果上面的示例过于冗长,复合注释允许减少需要在属性上声明的注释数量:

例 182.组合散列索引用法示例

@Document
public class DomainType {

@IndexAndHash(name = "idx...")
String value;

// ...
}

@Indexed
@HashIndexed
@Retention(RetentionPolicy.RUNTIME)
public @interface IndexAndHash {

@AliasFor(annotation = Indexed.class, attribute = "name")
String name() default "";
}

可能为元注释的某些属性注册别名。


尽管通过注释创建索引在许多情况下派上用场,但 cosider 通过手动设置索引来接管更多控制权。​​IndexOperations​




mongoOperations.indexOpsFor(Jedi.class)
.ensureIndex(HashedIndex.hashed("useTheForce"));



17.5.6. 通配符索引

A是一个索引,可用于包含基于给定(通配符)模式的所有字段或特定字段。 有关详细信息,请参阅MongoDB 文档。​​WildcardIndex​

可以使用 via 以编程方式设置索引。​​WildcardIndex​​​​IndexOperations​

例 183.编程通配符索引设置

mongoOperations
.indexOps(User.class)
.ensureIndex(new WildcardIndex("userMetadata"));
db.user.createIndex({ "userMetadata.$**" : 1 }, {})

注释允许声明性索引设置,该设置可以与文档类型或属性一起使用。​​@WildcardIndex​

如果放置在根级域实体(带有注释的类型)上,索引解析器将创建一个 它的通配符索引。​​@Document​

例 184.域类型的通配符索引

@Document
@WildcardIndexed
public class Product {
// …
}
db.product.createIndex({ "$**" : 1 },{})

可用于指定要在索引中输入/排除的键。​​wildcardProjection​

例 185.通配符索引​​wildcardProjection​

@Document
@WildcardIndexed(wildcardProjection = "{ 'userMetadata.age' : 0 }")
public class User {
private @Id String id;
private UserMetadata userMetadata;
}
db.user.createIndex(
{ "$**" : 1 },
{ "wildcardProjection" :
{ "userMetadata.age" : 0 }
}
)

通配符索引也可以通过将注释直接添加到字段中来表示。 请注意,在嵌套路径(如属性)上不允许这样做。 在索引创建过程中省略了对批注类型的投影。​​wildcardProjection​​​​@WildcardIndexed​

例 186.属性上的通配符索引

@Document
public class User {
private @Id String id;

@WildcardIndexed
private UserMetadata userMetadata;
}
db.user.createIndex({ "userMetadata.$**" : 1 }, {})

17.5.7. 文本索引

默认情况下,MongoDB v.2.4 的文本索引功能处于禁用状态。

创建文本索引允许将多个字段累积到可搜索的全文索引中。 每个集合只能有一个文本索引,因此所有标记为 without 的字段都合并到此索引中。 可以对属性进行加权以影响排名结果的文档分数。 文本索引的默认语言是 English.To 更改默认语言,将属性设置为所需的任何语言(例如,)。 使用调用的属性,可以在每个文档的基础上定义语言覆盖。 下面的示例演示如何创建文本索引并将语言设置为西班牙语:​​@TextIndexed​​​​language​​​​@Document(language="spanish")​​​​language​​​​@Language​

Example 187. Example Text Index Usage

@Document(language = "spanish")
class SomeEntity {

@TextIndexed String foo;

@Language String lang;

Nested nested;
}

class Nested {

@TextIndexed(weight=5) String bar;
String roo;
}

17.5.8. 使用 DBRefs

映射框架不必存储嵌入在文档中的子对象。 您也可以单独存储它们并使用 ato 引用该文档。 当对象从MongoDB加载时,这些引用会被急切地解析,以便你得到一个映射的对象,看起来就像它被嵌入在你的*文档中一样。​​DBRef​

下面的示例使用 DBRef 引用独立于引用它的对象而存在的特定文档(为简洁起见,这两个类都以内联方式显示):

@Document
public class Account {

@Id
private ObjectId id;
private Float total;
}

@Document
public class Person {

@Id
private ObjectId id;
@Indexed
private Integer ssn;
@DBRef
private List<Account> accounts;
}

您不需要使用类似的机制,因为对象列表告诉映射框架您想要一对多关系。 当对象存储在MongoDB中时,有一个DBRefs列表,而不是对象本身。 在加载集合时,建议将集合类型中保存的引用限制为特定的MongoDB集合。 这允许批量加载所有引用,而指向不同MongoDB集合的引用需要逐个解析。​​@OneToMany​​​​Account​​​​DBRef​

映射框架不处理级联保存。 如果更改对象引用的对象,则必须单独保存该对象。 调用对象不会自动将对象保存在属性中。​​Account​​​​Person​​​​Account​​​​save​​​​Person​​​​Account​​​​accounts​

​DBRef​​s也可以懒惰地解决。 在这种情况下,实际引用在首次访问属性时解析。 使用 属性 of 指定此项。 也定义为延迟加载并用作构造函数参数的必需属性也使用延迟加载代理进行装饰,以确保对数据库和网络施加尽可能小的压力。​​Object​​​​Collection​​​​lazy​​​​@DBRef​​​​DBRef​

延迟加载可能难以调试。 确保工具不会通过调用或一些内联调试呈现调用属性 getter 来意外触发代理解析。 请考虑启用跟踪日志记录以深入了解解决方案。​​DBRef​​​​toString()​​​​org.springframework.data.mongodb.core.convert.DefaultDbRefResolver​​​​DBRef​

延迟加载可能需要类代理,而类代理反过来可能需要访问 jdk 内部,这些内部结构从 Java 16+ 开始,由于JEP 396:默认情况下强封装 JDK 内部。​ 对于这些情况,请考虑回退到接口类型(例如,从切换到)或提供所需的参数。​​ArrayList​​​​List​​​​--add-opens​

17.5.9. 使用文档引用

Using提供了一种在MongoDB中引用实体的灵活方式。 虽然目标与使用DBRefs 时相同,但存储表示形式不同。解析为具有固定结构的文档,如MongoDB 参考文档中所述。
文档引用,不要遵循特定格式。 它们实际上可以是任何东西,单个值,整个文档,基本上可以存储在MongoDB中的所有内容。 默认情况下,映射图层将使用引用的实体id值进行存储和检索,如以下示例所示。​​@DocumentReference​​​​DBRef​

@Document
class Account {

@Id
String id;
Float total;
}

@Document
class Person {

@Id
String id;

@DocumentReference
List<Account> accounts;
}
Account account = …

tempate.insert(account);

template.update(Person.class)
.matching(where("id").is(…))
.apply(new Update().push("accounts").value(account))
.first();
{
"_id" : …,
"accounts" : [ "6509b9e" … ]
}

标记要引用的值的集合。​​Account​

映射框架不处理级联保存,因此请确保单独保留引用的实体。

添加对现有实体的引用。

引用实体表示为其值的数组。​​Account​​​​_id​

上面的示例使用基于提取查询 () 进行数据检索,并急切地解析链接实体。 可以使用以下属性更改分辨率默认值(如下所示)​​_id​​​​{ '_id' : ?#{#target} }​​​​@DocumentReference​

表 12.@DocumentReference默认值

属性

描述

违约

​db​

集合查找的目标数据库名称。

​MongoDatabaseFactory.getMongoDatabase()​

​collection​

目标集合名称。

带批注属性的域类型,分别是值类型(如果是类似或属性)、集合名称。​​Collection​​​​Map​

​lookup​

单个文档查找查询通过 SpEL 表达式评估占位符,用作给定源值的标记。like orproperties 通过运算符组合单个查找。​​#target​​​​Collection​​​​Map​​​​$or​

使用加载的源值进行基于安菲尔德的查询 ()。​​_id​​​​{ '_id' : ?#{#target} }​

​sort​

用于在服务器端对结果文档进行排序。

默认情况下为无。 类似属性的结果顺序基于已使用的查找查询在尽力而为的基础上还原。​​Collection​

​lazy​

如果设置为值,则解析将在首次访问属性时延迟。​​true​

默认情况下急切地解析属性。

延迟加载可能需要类代理,而类代理反过来可能需要访问 jdk 内部,这些内部结构从 Java 16+ 开始,由于JEP 396:默认情况下强封装 JDK 内部。​ 对于这些情况,请考虑回退到接口类型(例如,从切换到)或提供所需的参数。​​ArrayList​​​​List​​​​--add-opens​

​@DocumentReference(lookup)​​允许定义可以不同于 TheField 的过滤器查询,因此提供了一种灵活的方法来定义实体之间的引用,如下面的示例所示,其中 Of 一本书由其首字母缩略词而不是内部引用。​​_id​​​​Publisher​​​​id​

@Document
class Book {

@Id
ObjectId id;
String title;
List<String> author;

@Field("publisher_ac")
@DocumentReference(lookup = "{ 'acronym' : ?#{#target} }")
Publisher publisher;
}

@Document
class Publisher {

@Id
ObjectId id;
String acronym;
String name;

@DocumentReference(lazy = true)
List<Book> books;

}

​Book​​公文

{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher_ac" : "DR"
}

​Publisher​​公文

{
"_id" : 1a23e45,
"acronym" : "DR",
"name" : "Del Rey",

}


使用该字段查询集合中的实体。​​acronym​​​​Publisher​


延迟加载回对集合的引用。​​Book​

上面的代码片段显示了使用自定义引用对象时的阅读方面。 编写需要一些额外的设置,因为映射信息不表示来源。 映射层要求在目标文档和 a(如下所示)之间进行注册:​​#target​​​​Converter​​​​DocumentPointer​

@WritingConverter
class PublisherReferenceConverter implements Converter<Publisher, DocumentPointer<String>> {

@Override
public DocumentPointer<String> convert(Publisher source) {
return () -> source.getAcronym();
}
}

如果提供了 noconverter,则可以根据给定的查找查询计算目标参考文档。 在这种情况下,将评估关联目标属性,如以下示例所示。​​DocumentPointer​

@Document
class Book {

@Id
ObjectId id;
String title;
List<String> author;

@DocumentReference(lookup = "{ 'acronym' : ?#{acc} }")
Publisher publisher;
}

@Document
class Publisher {

@Id
ObjectId id;
String acronym;
String name;

// ...
}
{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher" : {
"acc" : "DOC"
}
}

使用该字段查询集合中的实体。​​acronym​​​​Publisher​

查找查询 (like) 的字段值占位符用于形成参考文档。​​acc​

也可以使用 and 的组合对关系样式一对多引用进行建模。 此方法允许链接类型,而无需将链接值存储在所属文档中,而是存储在引用文档上,如下例所示。​​@ReadonlyProperty​​​​@DocumentReference​

@Document
class Book {

@Id
ObjectId id;
String title;
List<String> author;

ObjectId publisherId;
}

@Document
class Publisher {

@Id
ObjectId id;
String acronym;
String name;

@ReadOnlyProperty
@DocumentReference(lookup="{'publisherId':?#{#self._id} }")
List<Book> books;
}

​Book​​公文

{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisherId" : 8cfb002
}

​Publisher​​ document

{
"_id" : 8cfb002,
"acronym" : "DR",
"name" : "Del Rey"
}

通过在文档中存储来设置从(引用)到(所有者)的链接。​​Book​​​​Publisher​​​​Publisher.id​​​​Book​

将保存引用的属性标记为只读。 这可以防止使用文档存储对个人的引用。​​Book​​​​Publisher​

使用变量访问文档中的值以及此检索匹配中的值。​​#self​​​​Publisher​​​​Books​​​​publisherId​

完成上述所有操作后,可以对实体之间的各种关联进行建模。 看看下面的非详尽示例列表,以了解可能性。

例 188.使用id字段的简单文档引用

class Entity {
@DocumentReference
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32"
}

// referenced object
{
"_id" : "9a48e32"
}


MongoDB简单类型可以直接使用,无需进一步配置。

例 189.使用带有显式查找查询的id字段的简单文档引用

class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{#target}' }")
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32"
}

// referenced object
{
"_id" : "9a48e32"
}

目标定义引用值本身。

例 190。文档引用 提取查找查询的字段​​refKey​

class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{refKey}' }")
private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("refKey", source.id);
}
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"refKey" : "9a48e32"
}
}

// referenced object
{
"_id" : "9a48e32"
}

用于获取参考值的密钥必须是写入时使用的密钥。

​refKey​​​是的简称。​​target.refKey​

例 191.具有构成查找查询的多个值的文档引用

class Entity {
@DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }")
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"fn" : "Josh",
"ln" : "Long"
}
}

// referenced object
{
"_id" : "9a48e32",
"firsntame" : "Josh",
"lastname" : "Long",
}


根据查找查询读取/调用键/从/到链接文档。​​fn​​​​ln​


使用非id字段查找目标文档。

例 192。从目标集合读取文档引用

class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}")
private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("id", source.id)
.append("collection", … );
}
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"id" : "9a48e32",
"collection" : "…"
}
}

从/到参考文档读取/调用键,以便在查找查询中使用它们。​​_id​

可以使用其键从参考文档中读取集合名称。


我们知道在查找查询中使用各种MongoDB查询运算符很诱人,这很好。 但有几个方面需要考虑:



  • 确保具有支持查找的索引。
  • 请注意,解析需要服务器漫游导致延迟,请考虑使用惰性策略。
  • 文档引用的集合是使用运算符大容量加载的。
    原始元素顺序在内存中尽最大努力恢复。 只有在使用相等表达式时才能恢复顺序,在使用 MongoDB 查询运算符时无法恢复顺序。 在这种情况下,结果将在从商店或通过提供的属性接收时进行排序。​​$or​​​​@DocumentReference(sort)​



更笼统地说:



  • 你使用循环引用吗? 问问自己是否需要它们。
  • 惰性文档引用很难调试。 确保工具不会通过调用等方式意外触发代理解析。​​toString()​
  • 不支持使用反应式基础结构读取文档引用。


17.5.10. 映射框架事件

事件在映射过程的整个生命周期中触发。生命周期事件部分对此进行了描述。

在 Spring ApplicationContext 中声明这些 bean 会导致每当调度事件时调用它们。

17.6. 解包类型

解包实体用于在 Java 域模型中设计值对象,这些对象的属性被展平到父级的 MongoDB 文档中。

17.6.1. 解包类型映射

考虑以下域模型,其中进行了注释。 注释表示应将 的所有属性展平到拥有该属性的文档中。​​User.name​​​​@Unwrapped​​​​@Unwrapped​​​​UserName​​​​user​​​​name​

例 193.解包对象的示例代码

class User {

@Id
String userId;

@Unwrapped(onEmpty = USE_NULL)
UserName name;
}

class UserName {

String firstname;

String lastname;

}
{
"_id" : "1da2ba06-3ba7",
"firstname" : "Emma",
"lastname" : "Frost"
}

加载属性时,其值设置为如果两者都不存在或不存在。 通过使用空,将创建具有其属性潜在价值的空。​​name​​​​null​​​​firstname​​​​lastname​​​​null​​​​onEmpty=USE_EMPTY​​​​UserName​​​​null​

对于不太详细的可嵌入类型声明,请使用和代替。 这两个注释都使用 JSR-305 进行元注释,以帮助进行可空性检查。​​@Unwrapped.Nullable​​​​@Unwrapped.Empty​​​​@Unwrapped(onEmpty = USE_NULL)​​​​@Unwrapped(onEmpty = USE_EMPTY)​​​​@javax.annotation.Nonnull​


可以在解包的对象中使用复杂类型。 但是,这些字段不能是,也不能包含未包装的字段本身。


17.6.2. 解开包装的类型字段名称

值对象可以使用注释的可选属性多次解包。 通过加样,所选前缀将附加到解包对象中的每个属性或名称前面。 请注意,如果多个属性呈现为相同的字段名称,则值将相互覆盖。​​prefix​​​​@Unwrapped​​​​@Field("…")​

例 194.带有名称前缀的解包对象的示例代码

class User {

@Id
String userId;

@Unwrapped.Nullable(prefix = "u_")
UserName name;

@Unwrapped.Nullable(prefix = "a_")
UserName name;
}

class UserName {

String firstname;

String lastname;
}
{
"_id" : "a6a805bd-f95f",
"u_firstname" : "Jean",
"u_lastname" : "Grey",
"a_firstname" : "Something",
"a_lastname" : "Else"
}

的所有属性都以 为前缀。​​UserName​​​​u_​

的所有属性都以 为前缀。​​UserName​​​​a_​

虽然将注释与完全相同的属性相结合是没有意义的,因此会导致错误。 这是在任何解包类型属性上使用的完全有效的方法。​​@Field​​​​@Unwrapped​​​​@Field​

例 195.使用注释解开对象的包装示例代码​​@Field​

public class User {

@Id
private String userId;

@Unwrapped.Nullable(prefix = "u-")
UserName name;
}

public class UserName {

@Field("first-name")
private String firstname;

@Field("last-name")
private String lastname;
}
{
"_id" : "2647f7b9-89da",
"u-first-name" : "Barbara",
"u-last-name" : "Gordon"
}

的所有属性都以 为前缀。​​UserName​​​​u-​

最终字段名称是串联的结果。​​@Unwrapped(prefix)​​​​@Field(name)​

17.6.3. 查询解包的对象

可以在类型和字段级别定义对未包装属性的查询,因为提供的查询与域类型匹配。 呈现实际查询时,将考虑前缀和潜在的自定义字段名称。 使用未包装对象的属性名称与所有包含的字段进行匹配,如下面的示例所示。​​Criteria​

例 196.对解包对象的查询

UserName userName = new UserName("Carol", "Danvers")
Query findByUserName = query(where("name").is(userName));
User user = template.findOne(findByUserName, User.class);
db.collection.find({
"firstname" : "Carol",
"lastname" : "Danvers"
})

也可以使用其属性名称直接对解包对象的任何字段进行寻址,如下面的代码片段所示。

例 197.查询解包对象的字段

Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
"firstname" : "Shuri"
})
按未换行的字段排序。

解包对象的字段可用于通过其属性路径进行排序,如下面的示例所示。

例 198。按未换行字段排序

Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
db.collection.find({
"lastname" : "Romanoff"
}).sort({ "firstname" : 1 })


尽管可能,但使用解包的对象本身作为排序条件会以不可预测的顺序包含其所有字段,并可能导致排序不准确。


未包装对象上的场投影

未包装对象的字段可以作为一个整体进行投影,也可以通过单个字段进行投影,如以下示例所示。

例 199.在解开包装的对象上投影。

Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name");
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
"lastname" : "Gamora"
},
{
"firstname" : 1,
"lastname" : 1
})

展开的对象上的场投影包括其所有属性。

例 200。投影到未包装对象的字段上。

Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname");
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
"lastname" : "Smoak"
},
{
"firstname" : 1
})

展开的对象上的场投影包括其所有属性。

按示例查询解开包装的对象。

解开包装的对象可以在 anprobe 中使用,就像任何其他类型的对象一样。 请查看按示例查询部分,了解有关此功能的详细信息。​​Example​

对解包对象的存储库查询。

抽象允许对解包对象的字段以及整个对象派生查询。​​Repository​

例 201.对解包对象的存储库查询。

interface UserRepository extends CrudRepository<User, String> {

List<User> findByName(UserName username);

List<User> findByNameFirstname(String firstname);
}

与解包对象的所有字段匹配。

比赛对阵。​​firstname​


即使 repositorynamespace 属性设置为 ,解包对象的索引创建也会挂起。​​create-query-indexes​​​​true​


17.6.4. 更新解包的对象

解包的对象可以更新为域模型中的任何其他对象。 映射图层负责将结构展平到周围环境中。 可以更新解包对象的单个属性以及整个值,如以下示例所示。

例 202。更新未换行对象的单个字段。

Update update = new Update().set("name.firstname", "Janet");
template.update(User.class).matching(where("id").is("Wasp"))
.apply(update).first()
db.collection.update({
"_id" : "Wasp"
},
{
"$set" { "firstname" : "Janet" }
},
{ ... }
)

Example 203. Update an unwrapped object.

Update update = new Update().set("name", new Name("Janet", "van Dyne"));
template.update(User.class).matching(where("id").is("Wasp"))
.apply(update).first()
db.collection.update({
"_id" : "Wasp"
},
{
"$set" {
"firstname" : "Janet",
"lastname" : "van Dyne",
}
},
{ ... }
)

17.6.5. Aggregations on Unwrapped Objects

聚合框架将尝试映射类型化聚合的解包值。 请确保在引用其值之一时使用属性路径,包括包装器对象。 除此之外,不需要采取任何特殊操作。

17.6.6. 解包对象的索引

可以将注释附加到未包装类型的属性,就像对常规对象所做的那样。 不能与拥有财产上的注释一起使用。​​@Indexed​​​​@Indexed​​​​@Unwrapped​

public class User {

@Id
private String userId;

@Unwrapped(onEmpty = USE_NULL)
UserName name;

// Invalid -> InvalidDataAccessApiUsageException
@Indexed
@Unwrapped(onEmpty = USE_Empty)
Address address;
}

public class UserName {

private String firstname;

@Indexed
private String lastname;
}

为集合创建的索引。​​lastname​​​​users​

无效用法以及​​@Indexed​​​​@Unwrapped​

17.7. 自定义转换 - 覆盖默认映射

影响映射结果的最简单方法是通过注释指定所需的本机MongoDB目标类型。这允许在持久化的同时使用非MongoDB类型,就像在域模型中一样 本机格式的值。​​@Field​​​​BigDecimal​​​​org.bson.types.Decimal128​

例 204.显式目标类型映射

public class Payment {

@Id String id;

@Field(targetType = FieldType.DECIMAL128)
BigDecimal value;

Date date;

}
{
"_id" : ObjectId("5ca4a34fa264a01503b36af8"),
"value" : NumberDecimal(2.099),
"date" : ISODate("2019-04-03T12:11:01.870Z")
}

表示验证的字符串id值将自动转换。有关详细信息,请参阅如何在映射图层中处理_id字段​。​​ObjectId​

所需的目标类型显式定义为转换为。否则,该值将被截断为a。​​Decimal128​​​​NumberDecimal​​​​BigDecimal​​​​String​

​Date​​​值由MongoDB驱动程序本身处理,并存储为。​​ISODate​

上面的代码段对于提供简单的类型提示非常方便。为了获得对映射过程的更精细的控制, 您可以使用实现注册 Spring 转换器,例如。​​MongoConverter​​​​MappingMongoConverter​

在尝试映射对象本身之前,检查是否有任何 Spring 转换器可以处理特定的类。要“劫持”正常的映射策略,也许是为了提高性能或其他自定义映射需求,您首先需要创建 Springinterface 的实现,然后将其注册到。​​MappingMongoConverter​​​​MappingMongoConverter​​​​Converter​​​​MappingConverter​


有关 Spring 类型转换服务的更多信息,请参阅​​此处​​的参考文档。

17.7.1. 使用已注册的弹簧转换器进行保存

下面的示例演示了从对象转换为 a 的实现:​​Converter​​​​Person​​​​org.bson.Document​

public class PersonWriteConverter implements Converter<Person, Document> {

public Document convert(Person source) {
Document document = new Document();
document.put("_id", source.getId());
document.put("name", source.getFirstName());
document.put("age", source.getAge());
return document;
}
}

17.7.2. 使用弹簧转换器读取

以下示例显示了从 ato aobject 转换的 a 的实现:​​Converter​​​​Document​​​​Person​

public class PersonReadConverter implements Converter<Document, Person> {

public Person convert(Document source) {
Person p = new Person((ObjectId) source.get("_id"), (String) source.get("name"));
p.setAge((Integer) source.get("age"));
return p;
}
}

17.7.3. 向​​MongoConverter​

class MyMongoConfiguration extends AbstractMongoClientConfiguration {

@Override
public String getDatabaseName() {
return "database";
}

@Override
protected void configureConverters(MongoConverterConfigurationAdapter adapter) {
adapter.registerConverter(new com.example.PersonReadConverter());
adapter.registerConverter(new com.example.PersonWriteConverter());
}
}

下面的 Spring实现示例从 ato 转换为自定义值对象:​​Converter​​​​String​​​​Email​

@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {

public Email convert(String source) {
return Email.valueOf(source);
}
}

如果您编写的源和目标类型是本机类型,则我们无法确定是否应将其视为读取或写入转换器。 将转换器实例注册为两者可能会导致不需要的结果。 例如,a是模棱两可的,尽管在编写时尝试将allinstance转换为实例可能没有意义。 为了让您强制基础结构仅以一种方式注册转换器,我们提供了要在转换器实现中使用的注释。​​Converter​​​​Converter<String, Long>​​​​String​​​​Long​​​​@ReadingConverter​​​​@WritingConverter​

转换器需要显式注册,因为不会从类路径或容器扫描中选取实例,以避免向转换服务进行不必要的注册以及此类注册产生的副作用。转换器注册为*工具,允许根据源类型和目标类型注册和查询已注册的转换器。​​CustomConversions​

​CustomConversions​​附带一组预定义的转换器注册:

  • JSR-310 转换器,用于类型之间的转换。java.timejava.util.DateString

本地时态类型 (e.g.to) 的默认转换器依赖于系统默认时区设置在这些类型之间进行转换。您可以通过注册自己的转换器来覆盖默认转换器。​​LocalDateTime​​​​java.util.Date​

转换器消歧

通常,我们会检查它们转换的源和目标类型的实现。 根据其中一个类型是否是基础数据访问 API 可以本机处理的类型,我们将转换器实例注册为读取或写入转换器。 以下示例显示了写入和读取转换器(请注意,区别在于限定符的顺序):​​Converter​​​​Converter​

// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> { … }

// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> { … }

17.8. 属性转换器 - 映射特定字段

虽然基于类型的转换已经提供了影响目标存储中某些类型的转换和表示的方法,但当仅应考虑特定类型的某些值或属性进行转换时,它具有限制。 基于属性的转换器允许以声明方式(通过)或编程方式(通过为特定属性注册 a)基于每个属性配置转换规则。​​@ValueConverter​​​​PropertyValueConverter​

Acan 可以将给定值转换为其存储表示形式(写入)和返回(读取),如以下清单所示。 附加项提供了其他信息,例如映射元数据和 directand方法。​​PropertyValueConverter​​​​ValueConversionContext​​​​read​​​​write​

例 205。一个简单的属性值转换器

class ReversingValueConverter implements PropertyValueConverter<String, String, ValueConversionContext> {

@Override
public String read(String value, ValueConversionContext context) {
return reverse(value);
}

@Override
public String write(String value, ValueConversionContext context) {
return reverse(value);
}
}

您可以通过委托从中获取实例,通常通过使用ato提供实际的转换器。 根据应用程序的需求,可以链接或修饰多个实例,例如,应用缓存。 默认情况下,Spring Data MongoDB使用缓存实现,该实现可以为具有默认构造函数或枚举值的类型提供服务。 一组预定义的工厂可以通过工厂方法获得。 您可以使用从 .​​PropertyValueConverter​​​​CustomConversions#getPropertyValueConverter(…)​​​​PropertyValueConversions​​​​PropertyValueConverterFactory​​​​PropertyValueConverterFactory​​​​PropertyValueConverterFactory​​​​PropertyValueConverterFactory.beanFactoryAware(…)​​​​PropertyValueConverter​​​​ApplicationContext​

您可以通过更改默认行为。​​ConverterConfiguration​

17.8.1. 声明式值转换器

ais 最直接的用法是使用定义转换器类型的注释来注释属性:​​PropertyValueConverter​​​​@ValueConverter​

例 206.声明性属性值转换器

class Person {

@ValueConverter(ReversingValueConverter.class)
String ssn;
}

17.8.2. 程序化价值转换器注册

编程注册使用 a 注册实体模型中属性的实例,如以下示例所示。 声明性注册和编程注册之间的区别在于,程序化注册完全发生在实体模型之外。 如果您不能或不想批注实体模型,则此方法非常有用。​​PropertyValueConverter​​​​PropertyValueConverterRegistrar​

例 207。程序化属性值转换器注册

PropertyValueConverterRegistrar registrar = new PropertyValueConverterRegistrar();

registrar.registerConverter(Address.class, "street", new PropertyValueConverter() { … });

// type safe registration
registrar.registerConverter(Person.class, Person::getSsn())
.writing(value -> encrypt(value))
.reading(value -> decrypt(value));

为由其名称标识的字段注册转换器。

允许注册转换器及其转换函数的类型安全变体。

注册转换器时,不支持用于跨属性到子文档中的点表示法(例如)。​​registerConverter(Person.class, "address.street", …)​

17.8.3. MongoDB 属性值转换

前面的各节概述了其总体结构的目的。 本节重点介绍MongoDB的具体方面。​​PropertyValueConverters​

MongoValueConverter 和 MongoConversionContext

​MongoValueConverter​​提供使用。​​PropertyValueConverter​​​​MongoConversionContext​

Mongo自定义转换配置

默认情况下,可以处理声明性值转换器,具体取决于配置的.helps设置编程值转换或定义要使用的值转换。​​MongoCustomConversions​​​​PropertyValueConverterFactory​​​​MongoConverterConfigurationAdapter​​​​PropertyValueConverterFactory​

例 208.配置示例

MongoCustomConversions.create(configurationAdapter -> {

SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions();
valueConversions.setConverterFactory(…);
valueConversions.setValueConverterRegistry(new PropertyValueConverterRegistrar()
.registerConverter(…)
.buildRegistry());

configurationAdapter.setPropertyValueConversions(valueConversions);
});

Spring Data (数据)MongoDB(四)

18. 分片

MongoDB通过分片支持大型数据集,分片是一种在多个数据库服务器之间分发数据的方法。 请参考MongoDB 文档,了解如何设置分片集群、其要求和限制。

Spring Data MongoDB使用注释来识别存储在分片集合中的实体,如下所示。​​@Sharded​

@Document("users")
@Sharded(shardKey = { "country", "userId" })
public class User {

@Id
Long id;

@Field("userid")
String userId;

String country;
}

分片键的属性映射到实际字段名称。

18.1. 分片集合

Spring Data MongoDB不会自动为集合或所需的索引设置分片。 下面的代码片段显示了如何使用MongoDB客户端API执行此操作。

MongoDatabase adminDB = template.getMongoDbFactory()
.getMongoDatabase("admin");

adminDB.runCommand(new Document("enableSharding", "db"));

Document shardCmd = new Document("shardCollection", "db.users")
.append("key", new Document("country", 1).append("userid", 1));

adminDB.runCommand(shardCmd);

需要针对管理数据库运行分片命令。

如有必要,为特定数据库启用分片。

对数据库中启用了分片的集合进行分片。

指定分片键。 此示例使用基于范围的分片。

18.2. 分片键处理

分片键由一个或多个属性组成,这些属性必须存在于目标集合中的每个文档中。 它用于跨分片分发文档。

向实体添加注释使Spring Data MongoDB能够应用分片场景所需的最大努力优化。 这意味着实质上是添加所需的分片键信息(如果尚未存在)以在更新插入实体时过滤查询。 这可能需要额外的服务器往返来确定当前分片键的实际值。​​@Sharded​​​​replaceOne​

通过设置Spring Data,不会尝试检查实体分片键是否已更改。​​@Sharded(immutableKey = true)​

有关更多详细信息,请参阅MongoDB 文档。 以下列表包含哪些操作符合分片键自动包含的条件:

  • ​(Reactive)CrudRepository.save(…)​
  • ​(Reactive)CrudRepository.saveAll(…)​
  • ​(Reactive)MongoTemplate.save(…)​

19. Kotlin 支持

Kotlin是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁优雅的代码,同时提供与用 Java 编写的现有库的出色互操作性。

Spring Data 为 Kotlin 提供了一流的支持,并允许开发人员编写 Kotlin 应用程序,就好像 Spring Data 是 Kotlin 原生框架一样。

使用 Kotlin 构建 Spring 应用程序的最简单方法是利用 Spring Boot 及其专用的 Kotlin 支持。 这个全面的教程将教你如何使用start.spring.io 使用 Kotlin 构建 Spring Boot 应用程序。

19.1. 要求

Spring Data 支持 Kotlin 1.3,并要求 kotlin-stdlib(或其变体之一,如kotlin-stdlib-jdk8)和kotlin-reflect 出现在类路径上。 如果您通过start.spring.io 引导 Kotlin 项目,则默认提供这些。

19.2. 空安全

Kotlin 的主要特性之一是空安全,它在编译时干净地处理值。 这通过可空性声明和“值或无值”语义的表达式使应用程序更安全,而无需支付包装器的成本,例如。 (Kotlin 允许使用具有可为空值的函数构造。请参阅此Kotlin 零安全综合指南。nullOptional

虽然Java不允许你在其类型系统中表达空安全,但Spring Data API是用JSR-305工具友好的注解来注释的。 默认情况下,Kotlin 中使用的 Java API 中的类型被识别为平台类型,其空检查被放宽。Kotlin 对 JSR-305 注释和 Spring 可空性注释的支持为 Kotlin 开发人员提供了整个 Spring 数据 API 的空安全性,具有在编译时处理相关问题的优势。org.springframework.langnull

请参阅存储库方法的空处理如何将空安全性应用于 Spring 数据存储库。


您可以通过使用以下选项添加编译器标志来配置 JSR-305 检查:​​-Xjsr305​​​​-Xjsr305={strict|warn|ignore}​



对于 Kotlin 版本 1.1+,默认行为与 相同。 该值是必需的,考虑到Spring Data API null-safety。Kotlin 类型是从 Spring API 推断出来的,但在使用 Spring API 可空性声明时,即使在次要版本之间也是如此,并且将来可能会添加更多检查。​​-Xjsr305=warn​​​​strict​


尚不支持泛型类型参数、varargs 和数组元素 nullability,但应该在即将发布的版本中支持。

19.3. 对象映射

请参阅 Kotlin支持,了解有关 Kotlin对象如何具体化的详细信息。

19.4. 扩展

Kotlin扩展提供了使用附加功能扩展现有类的能力。Spring Data Kotlin API 使用这些扩展为现有的 Spring API 添加新的特定于 Kotlin 的便利。


请记住,需要导入 Kotlin 扩展才能使用。 与静态导入类似,在大多数情况下,IDE 应自动建议导入。


例如,Kotlin 化类型参数为 JVM泛型类型擦除提供了一种解决方法,Spring Data 提供了一些扩展来利用此功能。 这允许更好的 Kotlin API。

要在 Java 中检索对象列表,您通常需要编写以下内容:​​SWCharacter​

Flux<SWCharacter> characters  = template.find(SWCharacter.class).inCollection("star-wars").all()

使用 Kotlin 和 Spring Data 扩展,您可以编写以下内容:

val characters = template.find<SWCharacter>().inCollection("star-wars").all()
// or (both are equivalent)
val characters : Flux<SWCharacter> = template.find().inCollection("star-wars").all()

与Java一样,Kotlin是强类型的,但Kotlin的聪明类型推断允许更短的语法。​​characters​

Spring Data MongoDB提供了以下扩展:

  • 具体化泛型支持,,,,和。MongoOperationsReactiveMongoOperationsFluentMongoOperationsReactiveFluentMongoOperationsCriteria
  • 针对 Kotlin 的类型安全查询
  • 协程扩展。ReactiveFluentMongoOperations

19.5. 协程

Kotlin协程是轻量级线程,允许命令性地编写非阻塞代码。 在语言端,函数为异步操作提供了抽象,而在库端,kotlinx.coroutines提供了像async { } 这样的函数和像Flow 这样的类型。suspend

Spring 数据模块在以下范围内为协程提供支持:

  • Kotlin 扩展中的延迟和流返回值支持
19.5.1. 依赖关系

当 和依赖项位于类路径中时,将启用协程支持:​​kotlinx-coroutines-core​​​​kotlinx-coroutines-reactive​​​​kotlinx-coroutines-reactor​

例 209。要在 Maven pom 中添加的依赖项.xml

<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactive</artifactId>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>

支持的版本及以上版本。​​1.3.0​

19.5.2. 响应式如何转换为协程?

对于返回值,从反应式 API 到协程 API 的转换如下所示:

  • ​fun handler(): Mono<Void>​​成为suspend fun handler()
  • ​fun handler(): Mono<T>​​变得取决于是否可以为空(具有更静态类型的优点)suspend fun handler(): Tsuspend fun handler(): T?Mono
  • ​fun handler(): Flux<T>​​成为fun handler(): Flow<T>

流在协程世界中是等效的,适用于热流或冷流、有限流或无限流,主要区别如下:Flux

  • Flow是基于推的,而是推挽混合的Flux
  • 通过悬挂功能实现背压
  • Flow只有一个挂起收集方法,运算符作为扩展实现
  • 借助协程,运算符易于实现
  • 扩展允许将自定义运算符添加到Flow
  • 收集操作正在暂停功能
  • map运算符支持异步操作(不需要),因为它需要一个挂起函数参数flatMap

阅读这篇关于使用Spring、协程和 Kotlin Flow 进行响应式的博客文章,了解更多详细信息,包括如何与协程同时运行代码。

19.5.3. 仓库

下面是协程存储库的示例:

interface CoroutineRepository : CoroutineCrudRepository<User, String> {

suspend fun findOne(id: String): User

fun findByFirstname(firstname: String): Flow<User>

suspend fun findAllByFirstname(id: String): List<User>
}

协程存储库建立在反应式存储库之上,通过 Kotlin 的协程公开数据访问的非阻塞性质。 协程存储库上的方法可以由查询方法或自定义实现提供支持。 如果自定义方法无需实现方法返回反应类型(如 asor),则调用自定义实现方法会将协程调用传播到实际实现方法。​​suspend​​​​Mono​​​​Flux​

请注意,根据方法声明,协程上下文可能可用,也可能不可用。 若要保留对上下文的访问权限,请声明方法using或返回启用上下文传播的类型,例如。​​suspend​​​​Flow​

  • ​suspend fun findOne(id: String): User​​:通过挂起同步检索数据一次。
  • ​fun findByFirstname(firstname: String): Flow<User>​​​:检索数据流。 在交互时获取数据时急切地创建 ()。​​Flow​​​​​Flow.collect(…)​
  • ​fun getUser(): User​​:在阻塞线程且没有上下文传播的情况下检索数据。 应避免这种情况。

仅当存储库扩展接口时,才会发现协程存储库。​​CoroutineCrudRepository​

20. JMX 支持

JMX 对 MongoDB 的支持公开了在单个 MongoDB 服务器实例的管理数据库上运行“serverStatus”命令的结果。它还公开了一个管理 MBean,允许您执行管理操作,例如删除或创建数据库。JMX功能建立在Spring Framework中可用的JMX功能集之上。有关更多详细信息,请参阅此处。​​MongoAdmin​

20.1. MongoDB JMX 配置

Spring 的 Mongo 命名空间允许您启用 JMX 功能,如以下示例所示:

例 210。用于配置 MongoDB 的 XML 模式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/mongo
https://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Default bean name is 'mongo' -->
<mongo:mongo-client host="localhost" port="27017"/>

<!-- by default look for a Mongo object named 'mongo' -->
<mongo:jmx/>

<context:mbean-export/>

<!-- To translate any MongoExceptions thrown in @Repository annotated classes -->
<context:annotation-config/>

<bean class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" p:port="1099" />

<!-- Expose JMX over RMI -->
<bean class="org.springframework.jmx.support.ConnectorServerFactoryBean"
depends-on="registry"
p:objectName="connector:name=rmi"
p:serviceUrl="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector" />

</beans>

前面的代码公开了几个 MBean:

  • ​AssertMetrics​
  • ​BackgroundFlushingMetrics​
  • ​BtreeIndexCounters​
  • ​ConnectionMetrics​
  • ​GlobalLockMetrics​
  • ​MemoryMetrics​
  • ​OperationCounters​
  • ​ServerInfo​
  • ​MongoAdmin​

JConsole 的以下屏幕截图显示了生成的配置:

Spring Data (数据)MongoDB(四)

附录

附录 A:命名空间引用

元素​​<repositories />​

该元素触发了 Spring 数据存储库基础结构的设置。最重要的属性是,它定义了要扫描 Spring 数据存储库接口的包。请参阅“XML 配置”。下表描述了元素的属性:​​<repositories />​​​​base-package​​​​<repositories />​

表 13.属性

名字

描述

​base-package​

定义要扫描的包,以查找在自动检测模式下扩展的存储库接口(实际接口由特定的 Spring 数据模块确定)。还会扫描已配置软件包下的所有软件包。允许使用通配符。​​*Repository​

​repository-impl-postfix​

定义用于自动检测自定义存储库实现的后缀。名称以配置的后缀结尾的类被视为候选类。默认为。​​Impl​

​query-lookup-strategy​

确定用于创建查找器查询的策略。有关详细信息,请参阅“查询查找策略​”。默认为。​​create-if-not-found​

​named-queries-location​

定义搜索包含外部定义查询的属性文件的位置。

​consider-nested-repositories​

是否应考虑嵌套存储库接口定义。默认为。​​false​

附录 B:填充器命名空间参考

<填充器 /> 元素

该元素允许通过 Spring 数据存储库基础架构填充数据存储。[​​4​]​<populator />​

表 14.属性

名字

描述

​locations​

应填充从存储库中查找要读取对象的文件的位置。

附录 C:存储库查询关键字

支持的查询方法主题关键字

下表列出了 Spring 数据存储库查询派生机制通常支持的主题关键字,以表达谓词。 有关支持的关键字的确切列表,请参阅特定于商店的文档,因为此处列出的某些关键字可能在特定商店中不受支持。

表 15.查询主题关键字

关键词

描述

​find…By​​​, , , , , ​​read…By​​​​get…By​​​​query…By​​​​search…By​​​​stream…By​

常规查询方法通常返回存储库类型、主子类型或结果包装器(如),或任何其他特定于存储的结果包装器。可以用作,或与其他关键字结合使用。​​Collection​​​​Streamable​​​​Page​​​​GeoResults​​​​findBy…​​​​findMyDomainTypeBy…​

​exists…By​

存在投影,返回通常为结果。​​boolean​

​count…By​

计数投影返回数值结果。

​delete…By​​​, ​​remove…By​

删除查询方法不返回任何结果 () 或删除计数。​​void​

​…First<number>…​​​, ​​…Top<number>…​

将查询结果限制为第一个结果。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​<number>​​​​find​​​​by​

​…Distinct…​

使用非重复查询仅返回唯一结果。请参阅特定于商店的文档是否支持该功能。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​find​​​​by​

支持的查询方法谓词关键字和修饰符

下表列出了 Spring 数据存储库查询派生机制通常支持的谓词关键字。 但是,请参阅特定于商店的文档,了解支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。

表 16.查询谓词关键字

逻辑关键字

关键字表达式

​AND​

​And​

​OR​

​Or​

​AFTER​

​After​​​, ​​IsAfter​

​BEFORE​

​Before​​​, ​​IsBefore​

​CONTAINING​

​Containing​​​, , ​​IsContaining​​​​Contains​

​BETWEEN​

​Between​​​, ​​IsBetween​

​ENDING_WITH​

​EndingWith​​​, , ​​IsEndingWith​​​​EndsWith​

​EXISTS​

​Exists​

​FALSE​

​False​​​, ​​IsFalse​

​GREATER_THAN​

​GreaterThan​​​, ​​IsGreaterThan​

​GREATER_THAN_EQUALS​

​GreaterThanEqual​​​, ​​IsGreaterThanEqual​

​IN​

​In​​​, ​​IsIn​

​IS​

​Is​​​,,(或无关键字)​​Equals​

​IS_EMPTY​

​IsEmpty​​​, ​​Empty​

​IS_NOT_EMPTY​

​IsNotEmpty​​​, ​​NotEmpty​

​IS_NOT_NULL​

​NotNull​​​, ​​IsNotNull​

​IS_NULL​

​Null​​​, ​​IsNull​

​LESS_THAN​

​LessThan​​​, ​​IsLessThan​

​LESS_THAN_EQUAL​

​LessThanEqual​​​, ​​IsLessThanEqual​

​LIKE​

​Like​​​, ​​IsLike​

​NEAR​

​Near​​​, ​​IsNear​

​NOT​

​Not​​​, ​​IsNot​

​NOT_IN​

​NotIn​​​, ​​IsNotIn​

​NOT_LIKE​

​NotLike​​​, ​​IsNotLike​

​REGEX​

​Regex​​​, , ​​MatchesRegex​​​​Matches​

​STARTING_WITH​

​StartingWith​​​, , ​​IsStartingWith​​​​StartsWith​

​TRUE​

​True​​​, ​​IsTrue​

​WITHIN​

​Within​​​, ​​IsWithin​

除了筛选器谓词之外,还支持以下修饰符列表:

表 17.查询谓词修饰符关键字

关键词

描述

​IgnoreCase​​​, ​​IgnoringCase​

与谓词关键字一起使用,用于不区分大小写的比较。

​AllIgnoreCase​​​, ​​AllIgnoringCase​

忽略所有合适属性的大小写。在查询方法谓词中的某处使用。

​OrderBy…​

指定静态排序顺序,后跟属性路径和方向(例如)。​​OrderByFirstnameAscLastnameDesc​

附录 D:存储库查询返回类型

支持的查询返回类型

下表列出了 Spring 数据存储库通常支持的返回类型。 但是,请参阅特定于商店的文档以获取支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。

地理空间类型(如、和)仅适用于支持地理空间查询的数据存储。 某些存储模块可能会定义自己的结果包装器类型。​​GeoResult​​​​GeoResults​​​​GeoPage​

表 18.查询返回类型

返回类型

描述

​void​

表示无返回值。

Java 原语。

包装器类型

Java 包装器类型。

​T​

一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​null​​​​IncorrectResultSizeDataAccessException​

​Iterator<T>​

一。​​Iterator​

​Collection<T>​

一个。​​Collection​

​List<T>​

一个。​​List​

​Optional<T>​

爪哇8或番石榴。期望查询方法最多返回一个结果。如果未找到结果,则返回 oris 。多个结果触发 an.​​Optional​​​​Optional.empty()​​​​Optional.absent()​​​​IncorrectResultSizeDataAccessException​

​Option<T>​

要么是斯卡拉,要么是vavrtype。在语义上与前面描述的Java 8的行为相同。​​Option​​​​Optional​

​Stream<T>​

A Java 8 .​​Stream​

​Streamable<T>​

该直接的便利扩展公开了流式传输,映射和过滤结果,连接它们等的方法。​​Iterable​

实现和采用构造函数或工厂方法参数的类型​​Streamable​​​​Streamable​

公开采用 aas 参数的构造函数或/工厂方法的类型。有关详细信息,请参阅返回自定义可流式传输包装器类型​。​​….of(…)​​​​….valueOf(…)​​​​Streamable​

瓦夫尔,,,​​Seq​​​​List​​​​Map​​​​Set​

Vavr 集合类型。有关详细信息,请参阅​​对 Vavr 集合的支持​​。

​Future<T>​

A. 期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​Future​​​​@Async​

​CompletableFuture<T>​

A Java 8.期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​CompletableFuture​​​​@Async​

​Slice<T>​

一个大小的数据块,指示是否有更多可用数据。需要方法参数。​​Pageable​

​Page<T>​

A 包含其他信息,例如结果总数。需要方法参数。​​Slice​​​​Pageable​

​GeoResult<T>​

包含附加信息(如到参考位置的距离)的结果条目。

​GeoResults<T>​

包含附加信息的列表,例如到参考位置的平均距离。​​GeoResult<T>​

​GeoPage<T>​

Awith,例如到参考位置的平均距离。​​Page​​​​GeoResult<T>​

​Mono<T>​

使用反应式存储库发射零个或一个元素的项目反应器。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Mono​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flux<T>​

使用反应式存储库发射零个、一个或多个元素的项目反应器。返回的查询还可以发出无限数量的元素。​​Flux​​​​Flux​

​Single<T>​

一个 RxJava使用反应式存储库发出单个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Single​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Maybe<T>​

使用反应式存储库的 RxJavaemitting 零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Maybe​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flowable<T>​

使用反应式存储库的 RxJavaemitting 零个、一个或多个元素。返回的查询还可以发出无限数量的元素。​​Flowable​​​​Flowable​