此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data MongoDB 4.4.0spring-doc.cadn.net.cn

对象映射

丰富的映射支持由MappingMongoConverter. 该转换器包含一个元数据模型,该模型提供了将域对象映射到 MongoDB 文档的完整功能集。 映射元数据模型是通过在域对象上使用注释来填充的。 但是,基础架构不仅限于使用注释作为元数据信息的唯一来源。 这MappingMongoConverter还允许您通过遵循一组约定将对象映射到文档,而无需提供任何其他元数据。spring-doc.cadn.net.cn

本节介绍MappingMongoConverter,包括基础知识、如何使用约定将对象映射到文档,以及如何使用基于批注的映射元数据覆盖这些约定。spring-doc.cadn.net.cn

对象映射基础知识

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

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

  1. 使用公开的构造函数之一创建实例。spring-doc.cadn.net.cn

  2. 实例填充 实现所有公开的属性。spring-doc.cadn.net.cn

对象创建

Spring Data 会自动尝试检测用于具体化该类型对象的持久实体的构造函数。 解析算法的工作原理如下:spring-doc.cadn.net.cn

  1. 如果有一个 static factory method,并带有@PersistenceCreator然后它被使用。spring-doc.cadn.net.cn

  2. 如果只有一个构造函数,则使用它。spring-doc.cadn.net.cn

  3. 如果有多个构造函数,并且只有一个构造函数被注释为@PersistenceCreator,则使用它。spring-doc.cadn.net.cn

  4. 如果类型是 JavaRecord使用 canonical 构造函数。spring-doc.cadn.net.cn

  5. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。spring-doc.cadn.net.cn

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

值 resolution 可以使用 Spring Framework 的@Valuevalue 注释。 有关更多详细信息,请参阅特定于 store 的映射部分。spring-doc.cadn.net.cn

对象创建内部

为了避免反射的开销, Spring Data 对象创建默认使用在运行时生成的工厂类,该类将直接调用域类构造函数。 例如,对于此示例类型:spring-doc.cadn.net.cn

class Person {
  Person(String firstname, String lastname) { … }
}

我们将在运行时创建一个语义上等同于此的工厂类:spring-doc.cadn.net.cn

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

这为我们提供了比反射大约 10% 的性能提升。 要使 domain 类有资格进行此类优化,它需要遵守一组约束:spring-doc.cadn.net.cn

如果这些条件中的任何一个匹配,Spring Data 将通过反射回退到实体实例化。spring-doc.cadn.net.cn

物业人口

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

  1. 如果该属性是不可变的,但公开了with…方法(见下文),我们使用with…方法创建具有新属性值的新实体实例。spring-doc.cadn.net.cn

  2. 如果定义了属性访问(即通过 getter 和 setter 进行访问),我们将调用 setter 方法。spring-doc.cadn.net.cn

  3. 如果属性是可变的,我们直接设置字段。spring-doc.cadn.net.cn

  4. 如果属性是不可变的,我们将使用持久性作(请参阅对象创建)使用的构造函数来创建实例的副本。spring-doc.cadn.net.cn

  5. 默认情况下,我们直接设置 field 值。spring-doc.cadn.net.cn

属性人口内部

我们在对象构造中的优化类似,我们也使用 Spring Data 运行时生成的访问器类来与实体实例进行交互。spring-doc.cadn.net.cn

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;
  }
}
生成的 Property Accessor
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的 PropertyAccessor 保存基础对象的可变实例。这是为了启用原本不可变属性的 mutation。
2 默认情况下, Spring Data 使用字段访问来读取和写入属性值。根据private领域MethodHandles用于与字段交互。
3 该类公开了一个withId(…)方法,例如,当实例插入数据存储并生成标识符时。叫withId(…)创建一个新的Person对象。所有后续更改都将在新实例中发生,而前一个实例保持不变。
4 使用 property-access 允许直接调用方法,而无需使用MethodHandles.

与反射相比,这为我们提供了大约 25% 的性能提升。 要使 domain 类有资格进行此类优化,它需要遵守一组约束:spring-doc.cadn.net.cn

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

让我们看看以下实体:spring-doc.cadn.net.cn

示例实体
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 identifier 属性是 final 的,但设置为null在构造函数中。 该类公开了一个withId(…)方法,例如,当实例插入数据存储并生成标识符时。 原版Person创建新实例时,实例保持不变。 相同的模式通常适用于存储管理的其他属性,但对于持久性作,可能需要更改这些属性。 wither 方法是可选的,因为持久化构造函数(参见 6)实际上是一个复制构造函数,并且设置该属性将被转换为创建一个应用了新标识符值的新实例。
2 firstnamelastnameproperties 是可能通过 getter 公开的普通不可变属性。
3 ageproperty 是一个不可变的,但派生自birthday财产。 显示设计后,数据库值将胜过默认值,因为 Spring Data 使用唯一声明的构造函数。 即使意图是应该首选计算,这个构造函数也接受ageas 参数(可能会忽略它),否则,属性 population 步骤将尝试设置 age 字段并失败,因为它是不可变的,并且没有with…方法。
4 commentproperty 是可变的,并通过直接设置其 field 来填充。
5 remarksproperty 是可变的,并通过调用 setter 方法进行填充。
6 该类公开了用于创建对象的工厂方法和构造函数。 这里的核心思想是使用工厂方法而不是额外的构造函数,以避免通过@PersistenceCreator. 相反,属性的默认值是在工厂方法中处理的。 如果您希望 Spring Data 使用工厂方法进行对象实例化,请使用@PersistenceCreator.

一般建议

  • 尽量坚持使用不可变对象 — 不可变对象很容易创建,因为具体化对象只需调用其构造函数即可。 此外,这还可以避免您的域对象充斥着允许客户端代码作对象状态的 setter 方法。 如果需要这些,最好将它们设为包保护,以便它们只能由有限数量的共存类型调用。 仅构造函数具体化比属性填充快 30%。spring-doc.cadn.net.cn

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

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

  • 请确保遵守允许使用生成的 instantiator 和 property 访问器类的约束spring-doc.cadn.net.cn

  • 对于要生成的标识符,仍将 final 字段与全参数持久性构造函数 (preferred) 或with…方法spring-doc.cadn.net.cn

  • 使用 Lombok 以避免样板代码 — 由于持久性作通常需要一个采用所有参数的构造函数,因此它们的声明会变成将样板参数繁琐地重复到字段分配中,而使用 Lombok 的@AllArgsConstructor.spring-doc.cadn.net.cn

覆盖属性

Java 允许灵活地设计域类,其中子类可以定义一个属性,该属性已经在其超类中以相同的名称声明。 请考虑以下示例:spring-doc.cadn.net.cn

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使用 Assignable 类型。SubType然而,阴影SuperType.field. 根据类设计,使用构造函数可能是设置SuperType.field. 或者,调用super.setField(…)可以在 setter 中设置fieldSuperType. 所有这些机制在某种程度上都会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 如果类型不可分配,则 Spring Data 会跳过超类型属性。 也就是说,被覆盖属性的类型必须可分配给其超类型属性类型才能注册为 override,否则超类型属性被视为临时属性。 我们通常建议使用不同的属性名称。spring-doc.cadn.net.cn

Spring Data 模块通常支持持有不同值的覆盖属性。 从编程模型的角度来看,需要考虑以下几点:spring-doc.cadn.net.cn

  1. 应该保留哪个属性(默认为所有声明的属性)? 您可以通过使用@Transient.spring-doc.cadn.net.cn

  2. 如何在数据存储中表示属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用显式字段/列名称至少注释一个属性。spring-doc.cadn.net.cn

  3. @AccessType(PROPERTY)不能使用,因为如果不对 setter 实现进行任何进一步的假设,通常不能设置 super-property。spring-doc.cadn.net.cn

Kotlin 支持

Spring Data 调整了 Kotlin 的细节以允许对象创建和更改。spring-doc.cadn.net.cn

Kotlin 对象创建

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

Spring Data 会自动尝试检测用于具体化该类型对象的持久实体的构造函数。 解析算法的工作原理如下:spring-doc.cadn.net.cn

  1. 如果存在一个带有@PersistenceCreator,则使用它。spring-doc.cadn.net.cn

  2. 如果类型是 Kotlin 数据类,则使用主构造函数。spring-doc.cadn.net.cn

  3. 如果有一个 static factory method,并带有@PersistenceCreator然后它被使用。spring-doc.cadn.net.cn

  4. 如果只有一个构造函数,则使用它。spring-doc.cadn.net.cn

  5. 如果有多个构造函数,并且只有一个构造函数被注释为@PersistenceCreator,则使用它。spring-doc.cadn.net.cn

  6. 如果类型是 JavaRecord使用 canonical 构造函数。spring-doc.cadn.net.cn

  7. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。spring-doc.cadn.net.cn

请考虑以下dataPerson:spring-doc.cadn.net.cn

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

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义这个类,并使用@PersistenceCreator要指示构造函数首选项,请执行以下作:spring-doc.cadn.net.cn

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

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

Kotlin 允许在未提供参数时使用默认值,从而支持参数可选性。 当 Spring Data 检测到具有参数default的构造函数时,如果数据存储不提供值(或只是返回)null),以便 Kotlin 可以应用参数默认值。请考虑以下类,该类为namespring-doc.cadn.net.cn

data class Person(var id: String, val name: String = "unknown")

每次nameparameter 不是 result 的一部分,或者其值为null,则name默认为unknown.spring-doc.cadn.net.cn

Spring Data 不支持委托属性。映射元数据会筛选 Kotlin Data 类的委托属性。 在所有其他情况下,您可以通过使用@delegate:org.springframework.data.annotation.Transient.

Kotlin 数据类的属性填充

在 Kotlin 中,默认情况下,所有类都是不可变的,并且需要显式属性声明来定义可变属性。 请考虑以下dataPerson:spring-doc.cadn.net.cn

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

这个类实际上是不可变的。 它允许在 Kotlin 生成copy(…)创建新对象实例的方法,从现有对象复制所有属性值,并将作为参数提供的属性值应用于方法。spring-doc.cadn.net.cn

Kotlin 覆盖属性

Kotlin 允许声明属性覆盖来更改子类中的属性。spring-doc.cadn.net.cn

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

这样的排列会呈现两个名为field. Kotlin 会为每个类中的每个属性生成属性访问器(getter 和 setter)。 实际上,代码如下所示:spring-doc.cadn.net.cn

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

上的 getter 和 setterSubType仅设置SubType.field而不是SuperType.field. 在这样的安排中,使用构造函数是将SuperType.field. 添加一个SubType设置SuperType.field通过this.SuperType.field = …是可能的,但不符合支持的约定。 属性覆盖在某种程度上会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 我们通常建议使用不同的属性名称。spring-doc.cadn.net.cn

Spring Data 模块通常支持持有不同值的覆盖属性。 从编程模型的角度来看,需要考虑以下几点:spring-doc.cadn.net.cn

  1. 应该保留哪个属性(默认为所有声明的属性)? 您可以通过使用@Transient.spring-doc.cadn.net.cn

  2. 如何在数据存储中表示属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用显式字段/列名称至少注释一个属性。spring-doc.cadn.net.cn

  3. @AccessType(PROPERTY)不能使用,因为无法设置 super-property。spring-doc.cadn.net.cn

Kotlin 值类

Kotlin 值类专为更具表现力的领域模型而设计,以明确基本概念。 Spring Data 可以使用 Value Classes 读取和写入定义属性的类型。spring-doc.cadn.net.cn

请考虑以下域模型:spring-doc.cadn.net.cn

@JvmInline
value class EmailAddress(val theAddress: String)                                    (1)

data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) (2)
1 具有不可为 null 的值类型的简单值类。
2 Data 类使用EmailAddressvalue 类。
使用非基元值类型的不可为 null 的属性在编译类中被平展为 value 类型。 可为 null 的基元值类型或可为 null 的值类型用其包装器类型表示,这会影响值类型在数据库中的表示方式。

基于约定的映射

MappingMongoConverter在未提供其他映射元数据时,有一些用于将对象映射到文档的约定。 约定是:spring-doc.cadn.net.cn

  • 短 Java 类名按以下方式映射到集合名。 类com.bigbank.SavingsAccount映射到savingsAccount集合名称。spring-doc.cadn.net.cn

  • 所有嵌套对象都作为嵌套对象存储在文档中,而不是 DBRefs 中。spring-doc.cadn.net.cn

  • 该转换器使用向其注册的任何 Spring 转换器来覆盖对象属性到文档字段和值的默认 Map。spring-doc.cadn.net.cn

  • 对象的字段用于与文档中的字段相互转换。 公共JavaBean属性。spring-doc.cadn.net.cn

  • 如果您有一个非零参数构造函数,其构造函数参数名称与 document 的顶级字段名称匹配,则使用该构造函数。否则,使用零参数构造函数。如果有多个非零参数构造函数,则会引发异常。spring-doc.cadn.net.cn

如何_idfield 在 mapping 层中处理。

MongoDB 要求您有一个_id字段。如果您未提供,驱动程序将分配一个具有生成值的 ObjectId。这_idfield 可以是除数组以外的任何类型,只要它是唯一的。驱动程序自然支持所有原始类型和 Dates。当使用MappingMongoConverter有一些规则控制如何将 Java 类中的属性映射到_id田。spring-doc.cadn.net.cn

下面概述了将映射到_iddocument 字段:spring-doc.cadn.net.cn

  • 带有@Id (org.springframework.data.annotation.Id) 将映射到_id田。
    此外,还可以通过
    @Fieldannotation 中,在这种情况下,文档将不包含字段_id.spring-doc.cadn.net.cn

  • 没有注释但名为id将映射到_id田。spring-doc.cadn.net.cn

表 1.翻译示例_id字段定义
字段定义 MongoDB 中生成的 Id-FieldName

String身份证spring-doc.cadn.net.cn

_idspring-doc.cadn.net.cn

@Field String身份证spring-doc.cadn.net.cn

_idspring-doc.cadn.net.cn

@Field("x") String身份证spring-doc.cadn.net.cn

xspring-doc.cadn.net.cn

@Id Stringxspring-doc.cadn.net.cn

_idspring-doc.cadn.net.cn

@Field("x") @Id Stringyspring-doc.cadn.net.cn

_id (@Field(name)被忽略,@Id优先)spring-doc.cadn.net.cn

下面概述了将对映射到 _id document 字段的属性执行的类型转换(如果有)。spring-doc.cadn.net.cn

  • 如果名为id在 Java 类中声明为 String 或 BigInteger,如果可能,它将被转换为并存储为 ObjectId。 ObjectId 作为字段类型也有效。 如果为id在您的应用程序中,到 ObjectId 的转换由 MongoDB 驱动程序完成。 如果指定了id值无法转换为 ObjectId,则该值将按原样存储在文档的_id田。 如果字段带有@Id.spring-doc.cadn.net.cn

  • 如果字段注释有@MongoId在 Java 类中,它将被转换为使用其实际类型进行存储。 除非@MongoId声明所需的字段类型。 如果没有为idfield 中,一个新的ObjectId将创建并转换为 Properties 类型。spring-doc.cadn.net.cn

  • 如果字段注释有@MongoId(FieldType.…)在 Java 类中,将尝试将值转换为声明的FieldType. 如果没有为idfield 中,一个新的ObjectId将创建并转换为 declared 的类型。spring-doc.cadn.net.cn

  • 如果名为id未在 Java 类中声明为 String、BigInteger 或 ObjectID,则应在应用程序中为其分配一个值,以便它可以“按原样”存储在文档的_id田。spring-doc.cadn.net.cn

  • 如果没有名为id存在于 Java 类中,则隐式的_id文件将由驱动程序生成,但不会映射到 Java 类的属性或字段。spring-doc.cadn.net.cn

查询和更新时MongoTemplate将使用转换器来处理QueryUpdate与上述保存文档规则相对应的对象,以便查询中使用的字段名称和类型能够匹配域类中的内容。spring-doc.cadn.net.cn

数据映射和类型转换

Spring Data MongoDB 支持可以表示为 MongoDB 的内部文档格式 BSON 的所有类型的类型。 除了这些类型之外, Spring Data MongoDB 还提供了一组内置转换器来映射其他类型。 您可以提供自己的转换器来调整类型转换。 有关更多详细信息,请参阅 自定义转化 - 覆盖默认映射spring-doc.cadn.net.cn

内置类型转换:
表 2.类型
类型 类型转换 样本

Stringspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"firstname" : "Dave"}spring-doc.cadn.net.cn

double,Double,float,Floatspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"weight" : 42.5}spring-doc.cadn.net.cn

int,Integer,short,Shortspring-doc.cadn.net.cn

本机
32 位整数spring-doc.cadn.net.cn

{"height" : 42}spring-doc.cadn.net.cn

long,Longspring-doc.cadn.net.cn

本机
64 位整数spring-doc.cadn.net.cn

{"height" : 42}spring-doc.cadn.net.cn

Date,Timestampspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

byte[]spring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"bin" : { "$binary" : "AQIDBA==", "$type" : "00" }}spring-doc.cadn.net.cn

java.util.UUID(旧版 UUID)spring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"uuid" : { "$binary" : "MEaf1CFQ6lSphaa3b9AtlA==", "$type" : "03" }}spring-doc.cadn.net.cn

Datespring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

ObjectIdspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"_id" : ObjectId("5707a2690364aba3136ab870")}spring-doc.cadn.net.cn

数组List,BasicDBListspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"cookies" : [ … ]}spring-doc.cadn.net.cn

boolean,Booleanspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"active" : true}spring-doc.cadn.net.cn

nullspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"value" : null}spring-doc.cadn.net.cn

Documentspring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"value" : { … }}spring-doc.cadn.net.cn

Decimal128spring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"value" : NumberDecimal(…)}spring-doc.cadn.net.cn

AtomicInteger
get()实际转换前spring-doc.cadn.net.cn

转换器
32 位整数spring-doc.cadn.net.cn

{"value" : "741" }spring-doc.cadn.net.cn

AtomicLong
get()实际转换前spring-doc.cadn.net.cn

转换器
64 位整数spring-doc.cadn.net.cn

{"value" : "741" }spring-doc.cadn.net.cn

BigIntegerspring-doc.cadn.net.cn

转炉
Stringspring-doc.cadn.net.cn

{"value" : "741" }spring-doc.cadn.net.cn

BigDecimalspring-doc.cadn.net.cn

转炉
Stringspring-doc.cadn.net.cn

{"value" : "741.99" }spring-doc.cadn.net.cn

URLspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"website" : "https://spring.io/projects/spring-data-mongodb/" }spring-doc.cadn.net.cn

Localespring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"locale : "en_US" }spring-doc.cadn.net.cn

char,Characterspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"char" : "a" }spring-doc.cadn.net.cn

NamedMongoScriptspring-doc.cadn.net.cn

转炉
Codespring-doc.cadn.net.cn

{"_id" : "script name", value: (some javascript code)}spring-doc.cadn.net.cn

java.util.Currencyspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"currencyCode" : "EUR"}spring-doc.cadn.net.cn

Instant
(Java 8)spring-doc.cadn.net.cn

本地spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

Instant
(Joda,JSR310-BackPort)spring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

LocalDate
(Joda、Java 8、JSR310-BackPort)spring-doc.cadn.net.cn

转换器 / 本机 (Java8)[1]spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T00:00:00.000Z")}spring-doc.cadn.net.cn

LocalDateTime,LocalTime
(Joda、Java 8、JSR310-BackPort)spring-doc.cadn.net.cn

转换器 / 本机 (Java8)[2]spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

DateTime“(乔达)spring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"date" : ISODate("2019-11-12T23:00:00.809Z")}spring-doc.cadn.net.cn

ZoneId(Java 8,JSR310-BackPort)spring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"zoneId" : "ECT - Europe/Paris"}spring-doc.cadn.net.cn

Boxspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"box" : { "first" : { "x" : 1.0 , "y" : 2.0} , "second" : { "x" : 3.0 , "y" : 4.0}}spring-doc.cadn.net.cn

Polygonspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"polygon" : { "points" : [ { "x" : 1.0 , "y" : 2.0} , { "x" : 3.0 , "y" : 4.0} , { "x" : 4.0 , "y" : 5.0}]}}spring-doc.cadn.net.cn

Circlespring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"circle" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}spring-doc.cadn.net.cn

Pointspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"point" : { "x" : 1.0 , "y" : 2.0}}spring-doc.cadn.net.cn

GeoJsonPointspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"point" : { "type" : "Point" , "coordinates" : [3.0 , 4.0] }}spring-doc.cadn.net.cn

GeoJsonMultiPointspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"geoJsonLineString" : {"type":"MultiPoint", "coordinates": [ [ 0 , 0 ], [ 0 , 1 ], [ 1 , 1 ] ] }}spring-doc.cadn.net.cn

Spherespring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"sphere" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}spring-doc.cadn.net.cn

GeoJsonPolygonspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"polygon" : { "type" : "Polygon", "coordinates" : [[ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 1 ], [ 0 , 0 ] ]] }}spring-doc.cadn.net.cn

GeoJsonMultiPolygonspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [ [ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ], [ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ] ] }}spring-doc.cadn.net.cn

GeoJsonLineStringspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{ "geoJsonLineString" : { "type" : "LineString", "coordinates" : [ [ 40 , 5 ], [ 41 , 6 ] ] }}spring-doc.cadn.net.cn

GeoJsonMultiLineStringspring-doc.cadn.net.cn

转炉spring-doc.cadn.net.cn

{"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [ [ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ], [ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ] ] }}spring-doc.cadn.net.cn

集合处理

集合处理取决于 MongoDB 返回的实际值。spring-doc.cadn.net.cn

  • 如果文档不包含映射到集合的字段,则映射不会更新属性。 这意味着该值将保持不变null、Java 默认值或在对象创建期间设置的任何值。spring-doc.cadn.net.cn

  • 如果文档包含要映射的字段,但该字段包含null值(例如:{ 'list' : null }),则属性值设置为null.spring-doc.cadn.net.cn

  • 如果文档包含要映射到集合的字段,而该字段不是 null(比如:{ 'list' : [ …​ ] }),则集合中会填充映射的值。spring-doc.cadn.net.cn

通常,如果使用 constructor creation,则可以获取要设置的值。 如果查询响应未提供属性值,则属性填充可以使用默认初始化值。spring-doc.cadn.net.cn

映射配置

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

您可以配置MappingMongoConverter以及com.mongodb.client.MongoClient和 MongoTemplate 使用基于 Java 或基于 XML 的元数据。 以下示例显示了配置:spring-doc.cadn.net.cn

@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

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

  // the following are optional

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

  @Override
  void configureConverters(MongoConverterConfigurationAdapter adapter) { (2)

  	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>();
  }
}
1 映射基础包定义了用于扫描用于预初始化MappingContext. 默认情况下,使用 configuration classes 包。
2 为特定域类型配置其他自定义转换器,以使用您的自定义实现替换这些类型的默认映射过程。
<?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:mongo="http://www.springframework.org/schema/data/mongo"
  xsi:schemaLocation="
    http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.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"/>

  <mongo:db-factory dbname="database" mongo-ref="mongoClient"/>

  <!-- by default look for a Mongo object named 'mongo' - default name used for the converter is 'mappingConverter' -->
  <mongo:mapping-converter base-package="com.bigbank.domain">
    <mongo:custom-converters>
      <mongo:converter ref="readConverter"/>
      <mongo:converter>
        <bean class="org.springframework.data.mongodb.test.PersonWriteConverter"/>
      </mongo:converter>
    </mongo:custom-converters>
  </mongo:mapping-converter>

  <bean id="readConverter" class="org.springframework.data.mongodb.test.PersonReadConverter"/>

  <!-- set the mapping converter to be used by the MongoTemplate -->
  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    <constructor-arg name="mongoConverter" ref="mappingConverter"/>
  </bean>

  <bean class="org.springframework.data.mongodb.core.mapping.event.LoggingEventListener"/>

</beans>

AbstractMongoClientConfiguration要求您实现定义com.mongodb.client.MongoClient以及提供数据库名称。AbstractMongoClientConfiguration还有一个名为getMappingBasePackage(…)您可以覆盖该 API 以告诉转换器在何处扫描带有@Document注解。spring-doc.cadn.net.cn

您可以通过覆盖customConversionsConfiguration方法。 MongoDB 的原生 JSR-310 支持可以通过以下方式启用MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs(). 前面的示例中还显示了一个LoggingEventListener记录MongoMappingEvent实例,这些实例被发布到 Spring 的ApplicationContextEvent基础设施。spring-doc.cadn.net.cn

Java 时间类型

我们建议通过以下方式使用 MongoDB 的原生 JSR-310 支持MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()如上所述,因为它使用UTC基于方法。 默认的 JSR-310 支持java.time从 Spring Data Commons 继承的类型使用本地机器时区作为参考,并且只应用于向后兼容。spring-doc.cadn.net.cn

AbstractMongoClientConfiguration创建一个MongoTemplateinstance 并将其注册到名为mongoTemplate.

base-package属性告诉它在何处扫描用@org.springframework.data.mongodb.core.mapping.Document注解。spring-doc.cadn.net.cn

如果您想依靠 Spring Boot 来引导 Data MongoDB,但仍希望覆盖配置的某些方面,则可能需要公开该类型的 bean。 对于自定义转换,您可以例如。选择注册MongoCustomConversions这将被 Boot 基础设施获取。 要了解有关此内容的更多信息,请务必阅读 Spring Boot 参考文档spring-doc.cadn.net.cn

基于元数据的映射

要充分利用 Spring Data MongoDB 支持中的对象映射功能,您应该使用@Document注解。 尽管 Map 框架不需要具有此 Comments(即使没有任何 Comments,您的 POJO 也被正确映射),但它允许 Classpath 扫描器查找并预处理您的域对象以提取必要的元数据。 如果不使用此注释,则应用程序在首次存储域对象时的性能会受到轻微影响,因为映射框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何持久保存它们。 以下示例显示了一个域对象:spring-doc.cadn.net.cn

示例 1.示例域对象
package com.mycompany.domain;

@Document
public class Person {

  @Id
  private ObjectId id;

  @Indexed
  private Integer ssn;

  private String firstName;

  @Indexed
  private String lastName;
}
@Idannotation 告诉映射器要用于 MongoDB 的属性_id属性和@Indexedannotation 告诉映射框架调用createIndex(…)在文档的该属性上,使搜索速度更快。 自动索引创建仅对使用@Document.
默认情况下,自动索引创建处于禁用状态,需要通过配置启用(请参阅索引创建)。

映射注释概述

MappingMongoConverter可以使用元数据来驱动对象到文档的 Map。 可以使用以下注释:spring-doc.cadn.net.cn

  • @Id:在字段级别应用,以标记用于身份目的的字段。spring-doc.cadn.net.cn

  • @MongoId:在字段级别应用,以标记用于身份目的的字段。 接受可选的FieldType自定义 ID 转换。spring-doc.cadn.net.cn

  • @Document:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定要存储数据的集合的名称。spring-doc.cadn.net.cn

  • @DBRef:在字段中应用,以指示要使用 com.mongodb.DBRef 进行存储。spring-doc.cadn.net.cn

  • @DocumentReference:在字段中应用,以指示它要存储为指向另一个文档的指针。 这可以是单个值(默认为 id),也可以是Document通过转换器提供。spring-doc.cadn.net.cn

  • @Indexed:在字段级别应用,用于描述如何为字段编制索引。spring-doc.cadn.net.cn

  • @CompoundIndex(可重复):在类型级别应用以声明 Compound Indexes。spring-doc.cadn.net.cn

  • @GeoSpatialIndexed:在字段级别应用,用于描述如何对字段进行地理索引。spring-doc.cadn.net.cn

  • @TextIndexed:在字段级别应用,以标记要包含在文本索引中的字段。spring-doc.cadn.net.cn

  • @HashIndexed:在字段级别应用于哈希索引中的使用,以跨分片集群对数据进行分区。spring-doc.cadn.net.cn

  • @Language:在字段级别应用以设置文本索引的语言覆盖属性。spring-doc.cadn.net.cn

  • @Transient:默认情况下,所有字段都映射到文档。 此 Comments 将应用它的字段排除在数据库中。 瞬态属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。spring-doc.cadn.net.cn

  • @PersistenceConstructor:标记给定的构造函数 - 甚至是受包保护的构造函数 - 在实例化数据库中的对象时使用。 构造函数参数按名称映射到检索到 Document 中的键值。spring-doc.cadn.net.cn

  • @Value:此注释是 Spring Framework 的一部分。在 Map 框架中,它可以应用于构造函数参数。 这允许你在使用 Spring Expression Language 语句来转换在数据库中检索到的键的值,然后再用于构造域对象。 为了引用给定文档的属性,必须使用如下表达式:@Value("#root.myProperty")哪里root引用给定文档的根。spring-doc.cadn.net.cn

  • @Field:在字段级别应用,它允许描述字段的名称和类型,因为它将在 MongoDB BSON 文档中表示,从而允许名称和类型与类的字段名称以及属性类型不同。spring-doc.cadn.net.cn

  • @Version:在字段级别应用用于乐观锁定,并检查保存作时的修改。 初始值为zero (one对于基元类型),该类型在每次更新时都会自动碰撞。spring-doc.cadn.net.cn

映射元数据基础结构在单独的spring-data-commons项目中定义,该项目与技术无关。 MongoDB 支持中使用特定的子类来支持基于 Comments 的元数据。 如果有需求,也可以采取其他策略。spring-doc.cadn.net.cn

下面是一个更复杂的映射示例
@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 类型与预期类型不匹配时,可以派上用场。 喜欢BigDecimal,表示为String而不是Decimal128,只是因为早期版本的 MongoDB Server 不支持它。spring-doc.cadn.net.cn

public class Balance {

  @Field(targetType = DECIMAL128)
  private BigDecimal value;

  // ...
}

您甚至可以考虑自己的自定义注释。spring-doc.cadn.net.cn

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

// ...

public class Balance {

  @Decimal128
  private BigDecimal value;

  // ...
}

特殊字段名称

一般来说,MongoDB 使用点 (.) 字符作为嵌套文档或数组的路径分隔符。 这意味着在查询(或 update 语句)中,像a.b.c以对象结构为目标,如下所述:spring-doc.cadn.net.cn

{
    'a' : {
        'b' : {
            'c' : …
        }
    }
}

因此,在 MongoDB 5.0 之前,字段名称不得包含点 (.).
使用
MappingMongoConverter#setMapKeyDotReplacement允许规避存储时的一些限制Map结构。spring-doc.cadn.net.cn

converter.setMapKeyDotReplacement("-");
// ...

source.map = Map.of("key.with.dot", "value")
converter.write(source,...) // -> map : { 'key-with-dot', 'value' }

随着 MongoDB 5.0 的发布,对Document包含特殊字符的字段名称已移除。 我们强烈建议您阅读 MongoDB 参考 中有关在字段名称中使用点的限制的更多信息。
允许
Map结构请设置preserveMapKeysMappingMongoConverter.spring-doc.cadn.net.cn

@Field允许自定义字段名称以两种方式考虑点。spring-doc.cadn.net.cn

  1. @Field(name = "a.b"):该名称被视为路径。 作需要嵌套对象的结构,例如{ a : { b : … } }.spring-doc.cadn.net.cn

  2. @Field(name = "a.b", fieldNameType = KEY):名称被视为原样名称。 作需要一个给定值为{ 'a.b' : ….. }spring-doc.cadn.net.cn

由于 MongoDB 查询和更新语句中点字符的特殊性质,包含点的字段名称不能直接作为目标,因此被排除在派生查询方法中使用之外。 请考虑以下Item具有categoryId属性映射到名为cat.id.spring-doc.cadn.net.cn

public class Item {

	@Field(name = "cat.id", fieldNameType = KEY)
	String categoryId;

	// ...
}

它的原始表示形式将如下所示spring-doc.cadn.net.cn

{
    'cat.id' : "5b28b5e7-52c2",
    ...
}

由于我们无法将cat.idfield 中(因为这会被解释为路径),我们需要 Aggregation Framework 的帮助。spring-doc.cadn.net.cn

名称中带有点的查询字段
template.query(Item.class)
    // $expr : { $eq : [ { $getField : { input : '$$CURRENT', 'cat.id' }, '5b28b5e7-52c2' ] }
    .matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("value")).equalToValue("5b28b5e7-52c2"))) (1)
    .all();
1 映射层负责翻译属性名称value添加到实际字段名称中。 在此处使用目标字段名称也是绝对有效的。
更新名称中带有点的字段
template.update(Item.class)
    .matching(where("id").is("r2d2"))
    // $replaceWith: { $setField : { input: '$$CURRENT', field : 'cat.id', value : 'af29-f87f4e933f97' } }
    .apply(AggregationUpdate.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("value", "af29-f87f4e933f97")))) (1)
    .first();
1 映射层负责翻译属性名称value添加到实际字段名称中。 在此处使用目标字段名称也是绝对有效的。

上面显示了一个简单的示例,其中特殊字段位于顶层文档级别。 嵌套级别的增加会增加与字段交互所需的聚合表达式的复杂性。spring-doc.cadn.net.cn

自定义对象构造

映射子系统允许通过使用@PersistenceConstructor注解。 要用于 constructor 参数的值按以下方式解析:spring-doc.cadn.net.cn

  • 如果参数使用@Valueannotation 中,将计算给定的表达式,并将结果用作参数值。spring-doc.cadn.net.cn

  • 如果 Java 类型具有名称与输入文档的给定字段匹配的属性,则其属性信息将用于选择适当的构造函数参数以将输入字段值传递给该参数。 仅当 java 中存在参数名称信息时,此选项才有效.class文件,可以通过使用调试信息编译源或使用新的-parametersJava 8 中 javac 的命令行开关。spring-doc.cadn.net.cn

  • 否则,一个MappingException将引发,指示无法绑定给定的 constructor 参数。spring-doc.cadn.net.cn

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注解的quantityparameter 回退到值0如果无法解析给定的属性路径。

使用@PersistenceConstructor注解可以在MappingMongoConverterUnitTests测试套件中找到。spring-doc.cadn.net.cn

映射框架事件

事件在映射过程的整个生命周期中触发。 这在 生命周期事件 部分中进行了介绍。spring-doc.cadn.net.cn

在 Spring ApplicationContext 中声明这些 bean 会导致在调度事件时调用它们。spring-doc.cadn.net.cn


1. 使用 UTC 时区偏移量。通过 MongoConverterConfigurationAdapter 配置
2. 使用 UTC 时区偏移量。通过 MongoConverterConfigurationAdapter 配置