Preface

©2008-2019 原作者。

Note

本文档的副本可以供您自己使用,也可以分发给其他人,但前提是您不对此类副本收取任何费用,并且还应确保每份副本均包含本版权声明(无论是印刷版本还是电子版本)。

Spring Data MongoDB 项目将 Spring 的核心概念应用于使用 MongoDB 文档样式数据存储的解决方案的开发中。我们提供“模板”作为用于存储和查询文档的高级抽象。您可能会注意到与 Spring 框架提供的 JDBC 支持相似。

本文档是 Spring Data-MongoDB 支持的参考指南。它说明了 MongoDB 模块概念以及各种 Store 名称空间的语义和语法。

本节提供了一些有关 Spring 和 Document 数据库的基本介绍。本文档的其余部分仅涉及 Spring Data MongoDB 功能,并假设用户熟悉 MongoDB 和 Spring 概念。

1.学习 Spring

Spring Data 使用 Spring 框架的core功能,包括:

虽然您不需要了解 Spring API,但了解它们背后的概念很重要。至少应该熟悉控制反转(IoC)的概念,并且您应该熟悉选择使用的任何 IoC 容器。

MongoDB 支持的核心功能可以直接使用,而无需调用 Spring 容器的 IoC 服务。这很像JdbcTemplate,可以在没有 Spring 容器的任何其他服务的情况下“独立”使用。要利用 Spring Data MongoDB 的所有功能,例如存储库支持,您需要配置库的某些部分以使用 Spring。

要了解有关 Spring 的更多信息,可以参考全面解释 Spring 框架的文档。关于该主题有很多文章,博客条目和书籍。有关更多信息,请参见 Spring 框架home page

2.学习 NoSQL 和文档数据库

NoSQLStore 席卷了存储世界。它是一个广阔的领域,具有许多解决方案,术语和模式(更糟糕的是,甚至术语本身也具有多个meanings)。尽管某些原则很常见,但是您必须在某种程度上熟悉 MongoDB。结识的最佳方法是阅读文档并按照示例进行操作。通常不需要花费 5 到 10 分钟的时间就可以完成这些练习,尤其是如果您来自仅 RDMBS 的背景,那么这些练习可能会让您大开眼界。

学习 MongoDB 的起点是www.mongodb.org。这是其他有用资源的列表:

3. Requirements

Spring Data MongoDB 2.x 二进制文件要求 JDK 级别 8.0 或更高以及Spring Framework 5.2.1.RELEASE 或更高。

在文档存储方面,您至少需要 2.6 版的MongoDB

4.其他帮助资源

学习新框架并不总是那么简单。在本节中,我们尝试提供我们认为是从 Spring Data MongoDB 模块开始易于理解的指南。但是,如果遇到问题或需要建议,请随时使用以下链接之一:

  • Community Forum

    • Stack Overflow上的 Spring Data 是所有 Spring Data(不仅仅是 Document)用户共享信息并互相帮助的标签。请注意,注册仅用于发布。
  • Professional Support

    • Spring Data 和 Spring 背后的公司Pivotal Sofware,Inc.可提供专业的,源码支持的响应时间。

5.后续开发

有关 Spring Data Mongo 源代码存储库,每晚构建和快照工件的信息,请参阅 Spring Data Mongo homepage。通过在Stack Overflow上通过社区与开发人员进行交互,可以帮助使 Spring Data 最好地满足 Spring 社区的需求。要跟踪开发人员的活动,请在 Spring Data Mongo homepage上查找邮件列表信息。如果您遇到错误或要提出改进建议,请在 Spring Data 问题tracker上创建故障单。要了解 Spring 生态系统中的最新新闻和公告,请订阅 Spring Community Portal。您也可以在 Spring blog或 Twitter(SpringData)上关注项目团队。

6.新功能

6.1. Spring Data MongoDB 2.2 的新增功能

  • 与 MongoDB 4.2 的兼容性,弃用evalgroupgeoNear Template API 方法。

  • 对 MongoDB 3.4 和 MongoDB 4.0 运算符的扩展 SpEL 聚合支持(请参阅投影表达式中的 Spring 表达式支持)。

  • Querydsl 对反应存储库的支持通过ReactiveQuerydslPredicateExecutor

  • 响应式 GridFS 支持.

  • Aggregation framework通过存储库查询方法提供支持。

  • 使用@Transactional的声明式被动事务。

  • 按实体删除模板 API 会在删除查询中考虑 version 属性。

  • 现在,无法删除版本控制的实体时,存储库删除将引发OptimisticLockingFailureException

  • 在两次查询之间的存储库中支持Range<T>

  • 现在,通过将* offset limit *传递给服务器,更改了Reactive/MongoOperations#count的行为,从而将范围限制在匹配数之内。

  • Update操作中支持数组过滤器。

  • JSON 模式生成来自域类型。

  • SpEL 支持@Indexed中的表达式。

  • 支持Hashed Indexes

  • 通过@Document@Query的基于 Comments 的归类支持。

  • Kotlin 的类型安全查询.

  • 现在不建议使用接受KClass的 Kotlin 扩展方法,而推荐使用reified方法。

  • Kotlin Coroutines支持。

6.2. Spring Data MongoDB 2.1 的新增功能

6.3. Spring Data MongoDB 2.0 的新功能

  • 升级到 Java 8.

  • Document API 而不是DBObject的用法。

  • ReactiveMongoDB 支持.

  • Tailable Cursor queries.

  • 通过使用 Java 8 Stream支持聚合结果流。

  • Fluent 的集合 API用于 CRUD 和聚合操作。

  • 模板和集合 API 的 Kotlin 扩展。

  • 排序规则的集成,以进行收集,索引创建以及查询操作。

  • 不带类型匹配的按示例查询支持。

  • 支持隔离Update操作。

  • 通过使用 Spring 的@NonNullApi@Nullable注解对 null-safety 的工具支持。

  • 弃用了跨存储支持,并删除了 Log4j 附加程序。

6.4. Spring Data MongoDB 1.10 的新增功能

  • 与 MongoDB Server 3.4 和 MongoDB Java 驱动程序 3.4 兼容。

  • @CountQuery@DeleteQuery@ExistsQuery的新 Comments。

  • 对 MongoDB 3.2 和 MongoDB 3.4 聚合运算符的扩展支持(请参阅支持的聚合操作)。

  • 创建索引时支持部分过滤器表达式。

  • 加载或转换DBRef个实例时发布生命周期事件。

  • 为示例查询添加了任何匹配模式。

  • 支持$caseSensitive$diacriticSensitive文本搜索。

  • 支持带孔的 GeoJSON 多边形。

  • 通过批量获取DBRef个实例来提高性能。

  • 使用$facet$bucket$bucketAutoAggregation进行多面聚合。

6.5. Spring Data MongoDB 1.9 的新增功能

  • 已启用以下 Comments 来构建您自己的组合 Comments:@Document@Id@Field@Indexed@CompoundIndex@GeoSpatialIndexed@TextIndexed@Query@Meta

  • 在存储库查询方法中支持Projections

  • 支持实例查询

  • 对象 Map 中对java.util.Currency的开箱即用支持。

  • 支持 MongoDB 2.6 中引入的批量操作。

  • 升级到 Querydsl 4.

  • 声明与 MongoDB 3.0 和 MongoDB Java 驱动程序 3.2 的兼容性(请参阅:MongoDB 3.0 支持)。

6.6. Spring Data MongoDB 1.8 的新增功能

  • Criteria支持创建$geoIntersects

  • 支持@Query中的SpEL expressions

  • MongoMappingEvents公开为其发出的集合名称。

  • 改进了对<mongo:mongo-client credentials="…" />的支持。

  • 改进了索引创建失败错误消息。

6.7. Spring Data MongoDB 1.7 的新增功能

  • 声明与 MongoDB 3.0 和 MongoDB Java 驱动程序 3-beta3 的兼容性(请参阅:MongoDB 3.0 支持)。

  • 支持 JSR-310 和 ThreeTen 反向端口日期/时间类型。

  • 允许Stream作为查询方法的返回类型(请参阅:Query Methods)。

  • GeoJSON支持域类型和查询(请参阅:GeoJSON Support)。

  • QueryDslPredicateExcecutor现在支持findAll(OrderSpecifier<?>… orders)

  • 支持使用Script Operations调用 JavaScript 函数。

  • 改进对类似于集合的属性的CONTAINS关键字的支持。

  • 支持Update$bit$mul$position运算符。

7. Dependencies

由于各个 Spring Data 模块的起始日期不同,因此大多数模块带有不同的主要和次要版本号。查找兼容版本的最简单方法是依赖于我们附带定义的兼容版本的 Spring Data Release Train BOM。在 Maven 项目中,您可以在 POM 的<dependencyManagement />部分中声明此依赖关系,如下所示:

例子 1.使用 Spring Data Release 系列 BOM

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-releasetrain</artifactId>
      <version>Moore-SR1</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

当前的发行版本是Moore-SR1。火车名称按字母 Sequences 升序,当前可用的火车列于here。版本名称遵循以下模式:${name}-${release},其中 release 可以是以下之一:

  • BUILD-SNAPSHOT:当前快照

  • M1M2,依此类推:里程碑

  • RC1RC2,依此类推:发布候选

  • RELEASE:GA 发布

  • SR1SR2等:服务版本

可以在我们的Spring Data 示例存储库中找到使用 BOM 的工作示例。有了它,您可以在<dependencies />块中声明要使用的 Spring Data 模块而无需版本,如下所示:

例子 2.声明对 Spring Data 模块的依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

7.1. 使用 Spring Boot 进行依赖 Management

Spring Boot 为您选择了 Spring Data 模块的最新版本。如果仍要升级到较新的版本,请将属性spring-data-releasetrain.version配置为要使用的火车名称和迭代

7.2. Spring Framework

当前版本的 Spring Data 模块要求使用 5.2.1.RELEASE 或更高版本的 Spring Framework。这些模块也可以与该次要版本的较旧错误修正版本一起使用。但是,强烈建议使用该版本中的最新版本。

8.使用 Spring 数据存储库

Spring 数据存储库抽象的目标是显着减少实现各种持久性存储的数据访问层所需的样板代码量。

Tip

  • Spring 数据存储库文档和您的模块*

本章介绍了 Spring Data 存储库的核心概念和接口。本章中的信息来自 Spring Data Commons 模块。它使用 Java Persistence API(JPA)模块的配置和代码示例。您应该使 XML 名称空间声明和类型适应于所使用的特定模块的等效项。 “ Namespace reference”涵盖 XML 配置,所有支持存储库 API 的 Spring Data 模块均支持该配置。 “ Repositories 查询关键字”通常涵盖存储库抽象支持的查询方法关键字。有关模块的特定功能的详细信息,请参阅本文档中有关该模块的章节。

8.1. 核心概念

Spring Data 存储库抽象中的中央接口是Repository。它需要域类以及域类的 ID 类型作为类型参数来进行 Management。该接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展该接口的接口。 CrudRepository为正在 Management 的实体类提供复杂的 CRUD 功能。

例子 3. CrudRepository界面

public interface CrudRepository<T, ID> extends Repository<T, ID> {

  <S extends T> S save(S entity);      (1)

  Optional<T> findById(ID primaryKey); (2)

  Iterable<T> findAll();               (3)

  long count();                        (4)

  void delete(T entity);               (5)

  boolean existsById(ID primaryKey);   (6)

  // … more functionality omitted.
}
  • (1) 保存给定的实体。
  • (2) 返回由给定 ID 标识的实体。
  • (3) 返回所有实体。
  • (4) 返回实体数。
  • (5) 删除给定的实体。
  • (6) 指示是否存在具有给定 ID 的实体。

Note

我们还提供特定于持久性技术的抽象,例如JpaRepositoryMongoRepository。这些接口扩展了CrudRepository,并且除了诸如CrudRepository之类的与通用技术无关的通用接口之外,还公开了底层持久性技术的功能。

CrudRepository之上,有一个PagingAndSortingRepository抽象,它添加了其他方法来简化对实体的分页访问:

例子 4. PagingAndSortingRepository界面

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

要以 20 页的大小访问User的第二页,您可以执行以下操作:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));

除了查询方法之外,还可以使用计数和删除查询的查询派生。以下列表显示派生计数查询的接口定义:

例子 5.派生计数查询

interface UserRepository extends CrudRepository<User, Long> {

  long countByLastname(String lastname);
}

以下列表显示了派生的删除查询的接口定义:

例子 6.派生的删除查询

interface UserRepository extends CrudRepository<User, Long> {

  long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);
}

8.2. 查询方法

标准 CRUD 功能存储库通常在基础数据存储上进行查询。使用 Spring Data,声明这些查询将分为四个步骤:

  • 声明扩展存储库的接口或其子接口之一,然后将其键入到它应处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … }
  • 在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> {
  List<Person> findByLastname(String lastname);
}
  • 设置 Spring 以使用JavaConfigXML configuration为这些接口创建代理实例。

  • 要使用 Java 配置,请创建类似于以下内容的类:

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories
class Config { … }
  • 要使用 XML 配置,请定义类似于以下内容的 bean:
<?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:jpa="http://www.springframework.org/schema/data/jpa"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
     https://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/data/jpa
     https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

   <jpa:repositories base-package="com.acme.repositories"/>

</beans>

在此示例中使用了 JPA 命名空间。如果将存储库抽象用于任何其他 Store,则需要将其更改为 Store 模块的适当名称空间声明。换句话说,您应该将jpa换成mongodb

  • 另外,请注意,JavaConfig 变量不会显式配置程序包,因为默认情况下使用带 Comments 的类的程序包。要自定义要扫描的包,请使用特定于数据存储的存储库@Enable${store}Repositories-Comments 的basePackage…属性之一。
  • 注入存储库实例并使用它,如以下示例所示:
class SomeClient {

  private final PersonRepository repository;

  SomeClient(PersonRepository repository) {
    this.repository = repository;
  }

  void doSomething() {
    List<Person> persons = repository.findByLastname("Matthews");
  }
}

以下各节详细说明了每个步骤:

8.3. 定义存储库接口

首先,定义特定于域类的存储库接口。该接口必须扩展为Repository,并且必须键入域类和 ID 类型。如果要公开该域类型的 CRUD 方法,请扩展CrudRepository而不是Repository

8.3.1. 微调存储库定义

通常,您的存储库界面扩展了RepositoryCrudRepositoryPagingAndSortingRepository。另外,如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinitionComments 存储库接口。扩展CrudRepository公开了一套完整的方法来操纵您的实体。如果您希望对公开的方法保持选择性,请将要公开的方法从CrudRepository复制到域存储库中。

Note

这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。

以下示例显示如何有选择地公开 CRUD 方法(在本例中为findByIdsave):

例子 7.有选择地公开 CRUD 方法

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

在前面的示例中,您为所有域存储库定义了一个通用的基本接口,并公开了findById(…)save(…)这些方法将路由到 Spring Data 提供的所选存储的基本存储库实现中(例如,如果您使用 JPA,实现为SimpleJpaRepository),因为它们与CrudRepository中的方法签名匹配。因此,UserRepository现在可以保存用户,通过 ID 查找单个用户,并触发查询以通过电子邮件地址查找Users

Note

中间存储库接口用@NoRepositoryBeanComments。确保将 Comments 添加到所有存储库接口,Spring Data 不应在运行时为其创建实例。

8.3.2. 将存储库与多个 Spring 数据模块一起使用

在您的应用程序中使用唯一的 Spring Data 模块使事情变得简单,因为已定义范围中的所有存储库接口均已绑定到 Spring Data 模块。有时,应用程序需要使用多个 Spring Data 模块。在这种情况下,存储库定义必须区分持久性技术。当它在 Classpath 上检测到多个存储库工厂时,Spring Data 进入严格的存储库配置模式。严格的配置使用存储库或域类上的详细信息来决定有关存储库定义的 Spring Data 模块绑定:

  • 如果存储库定义为扩展特定模块的存储库,则它是特定 Spring Data 模块的有效候选者。

  • 如果域类是使用模块特定的类型 Comments 进行 Comments,那么它是特定 Spring Data 模块的有效候选者。 Spring Data 模块可以接受第三方 Comments(例如 JPA 的@Entity),也可以提供自己的 Comments(例如,针对 Spring Data MongoDB 和 Spring Data Elasticsearch 的@Document)。

以下示例显示了使用模块特定接口(在这种情况下为 JPA)的存储库:

例子 8.使用模块特定接口的存储库定义

interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }

interface UserRepository extends MyBaseRepository<User, Long> { … }

MyRepositoryUserRepository在其类型层次结构中扩展JpaRepository。它们是 Spring Data JPA 模块的有效候选者。

以下示例显示了使用通用接口的存储库:

示例 9.使用通用接口的存储库定义

interface AmbiguousRepository extends Repository<User, Long> { … }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }

AmbiguousRepositoryAmbiguousUserRepository在其类型层次结构中仅扩展RepositoryCrudRepository。尽管在使用唯一的 Spring Data 模块时这很好,但是多个模块无法区分这些存储库应绑定到哪个特定的 Spring Data。

以下示例显示了使用带 Comments 的域类的存储库:

例子 10.使用带有 Comments 的域类的存储库定义

interface PersonRepository extends Repository<Person, Long> { … }

@Entity
class Person { … }

interface UserRepository extends Repository<User, Long> { … }

@Document
class User { … }

PersonRepository引用Person,并使用 JPA @EntityComments 进行了 Comments,因此该存储库显然属于 Spring Data JPA。 UserRepository引用User,使用 Spring Data MongoDB 的@DocumentComments 进行 Comments。

以下不良示例显示了使用带有混合注解的域类的存储库:

例子 11.使用带有混合 Comments 的域类的存储库定义

interface JpaPersonRepository extends Repository<Person, Long> { … }

interface MongoDBPersonRepository extends Repository<Person, Long> { … }

@Entity
@Document
class Person { … }

此示例显示了同时使用 JPA 和 Spring Data MongoDB 注解的域类。它定义了两个存储库JpaPersonRepositoryMongoDBPersonRepository。一个用于 JPA,另一个用于 MongoDB。 Spring Data 不再能够区分存储库,这导致不确定的行为。

Repositories 类型详细信息区分领域类 Comments用于严格的存储库配置,以标识特定 Spring Data 模块的存储库候选者。在同一个域类型上使用多个特定于持久性技术的 Comments 是可能的,并且可以跨多种持久性技术重用域类型。但是,Spring Data 无法再确定用于绑定存储库的唯一模块。

区分存储库的最后一种方法是确定存储库基础包的范围。基本软件包定义了扫描存储库接口定义的起点,这意味着将存储库定义放在适当的软件包中。默认情况下,Comments 驱动的配置使用配置类的包。 基于 XML 的配置中的基本软件包是必填项。

以下示例显示了基础包的 Comments 驱动配置:

例子 12.基础包的 Comments 驱动配置

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }

8.4. 定义查询方法

存储库代理有两种从方法名称派生特定于 Store 的查询的方式:

  • 通过直接从方法名称派生查询。

  • 通过使用手动定义的查询。

可用选项取决于实际 Store。但是,必须有一个策略来决定要创建的实际查询。下一节将介绍可用的选项。

8.4.1. 查询查询策略

以下策略可用于存储库基础结构来解决查询。使用 XML 配置,您可以通过query-lookup-strategy属性在名称空间中配置策略。对于 Java 配置,可以使用Enable${store}Repositories注解的queryLookupStrategy属性。某些数据存储可能不支持某些策略。

  • CREATE尝试从查询方法名称构造特定于 Store 的查询。通用方法是从方法名称中删除一组给定的众所周知的前缀,然后解析该方法的其余部分。您可以在“ Query Creation”中阅读有关查询构造的更多信息。

  • USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到则抛出异常。该查询可以通过某处的 Comments 定义,也可以通过其他方式声明。请查阅特定 Store 的文档以找到该 Store 的可用选项。如果存储库基础结构在引导时找不到该方法的声明查询,则它将失败。

  • CREATE_IF_NOT_FOUND(默认)组合了CREATEUSE_DECLARED_QUERY。它首先查找一个声明的查询,如果找不到声明的查询,它将创建一个基于名称的自定义方法查询。这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略。它允许通过方法名称快速定义查询,还可以通过根据需要引入已声明的查询来自定义调整这些查询。

8.4.2. 查询创建

内置在 Spring Data 存储库基础结构中的查询构建器机制对于在存储库实体上构建约束查询很有用。该机制从方法中剥离前缀find…Byread…Byquery…Bycount…Byget…By,然后开始解析其余部分。 Introduction 子句可以包含其他表达式,例如Distinct,以在要创建的查询上设置不同的标志。但是,第一个By充当分隔符,以指示实际标准的开始。在最基本的级别上,您可以定义实体属性的条件,并将它们与AndOr串联。以下示例显示了如何创建许多查询:

例子 13.从方法名查询创建

interface PersonRepository extends Repository<Person, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析该方法的实际结果取决于您为其创建查询的持久性存储。但是,需要注意一些一般事项:

  • 表达式通常是属性遍历,并带有可串联的运算符。您可以将属性表达式与ANDOR结合使用。您还将获得对属性表达式的支持,例如BetweenLessThanGreaterThanLike。支持的运算符可能因数据存储而异,因此请参考参考文档的相应部分。

  • 方法解析器支持为单个属性(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常为String instance_,例如findByLastnameAndFirstnameAllIgnoreCase(…))设置IgnoreCase标志。是否支持忽略大小写可能因 Store 而异,因此请参考参考文档中有关 Store 特定查询方法的相关部分。

  • 您可以通过将OrderBy子句附加到引用属性的查询方法并提供排序方向(AscDesc)来应用静态排序。要创建支持动态排序的查询方法,请参阅“ 特殊参数处理”。

8.4.3. 属性表达式

如上例所示,属性表达式只能引用被管实体的直接属性。在查询创建时,您已经确保已解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:

List<Person> findByAddressZipCode(ZipCode zipCode);

假设Person具有AddressZipCode。在这种情况下,该方法将创建属性遍历x.address.zipCode。解析算法首先将整个部分(AddressZipCode)解释为属性,然后在域类中检查具有该名称的属性(未大写)。如果算法成功,它将使用该属性。如果不是,该算法将驼峰案例部分的源从右侧分为头和尾,并尝试找到相应的属性,在我们的示例中为AddressZipCode。如果该算法找到了具有该头部的属性,则将其取为尾部,并 continue 从此处开始构建树,以刚才描述的方式将尾部向上拆分。如果第一个分割不匹配,则算法将分割点移到左侧(AddressZipCode)并 continue。

尽管这在大多数情况下应该可行,但算法可能会选择错误的属性。假设Person类也具有addressZip属性。该算法将在第一轮拆分中已经匹配,选择错误的属性,然后失败(因为addressZip的类型可能没有code属性)。

要解决这种歧义,您可以在方法名称中使用_来手动定义遍历点。因此,我们的方法名称如下:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准 Java 命名约定(即,在属性名称中不使用下划线,而使用驼峰大小写)。

8.4.4. 特殊参数处理

要处理查询中的参数,请定义方法参数,如前面的示例所示。除此之外,基础架构还可以识别某些特定类型(例如PageableSort),以将分页和排序动态应用于您的查询。下面的示例演示了这些功能:

例子 14.在查询方法中使用PageableSliceSort

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

Tip

具有SortPageable的 API 期望将非null的值传递到方法中。如果您不想应用任何排序或分页,请使用Sort.unsorted()Pageable.unpaged()

第一种方法使您可以将org.springframework.data.domain.Pageable实例传递给查询方法,以将分页动态添加到静态定义的查询中。 Page知道可用元素和页面的总数。它是通过基础结构触发计数查询来计算总数来实现的。由于这可能很昂贵(取决于使用的 Store),因此您可以返回SliceSlice仅知道下一个Slice是否可用,当遍历较大的结果集时可能就足够了。

排序选项也通过Pageable实例处理。如果只需要排序,则将org.springframework.data.domain.Sort参数添加到您的方法中。如您所见,返回List也是可能的。在这种情况下,将不会创建构建实际Page实例所需的其他元数据(这反过来,这不会发出原本必须的附加计数查询)。而是,它将查询限制为仅查找给定范围的实体。

Note

要查明整个查询可获得多少页,您必须触发另一个计数查询。默认情况下,此查询源自您实际触发的查询。

分页和排序

可以使用属性名称定义简单的排序表达式。可以将表达式连接起来,以将多个条件收集到一个表达式中。

例子 15.定义排序表达式

Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

对于定义排序表达式的类型安全性更高的方法,请从该类型开始为定义排序表达式,然后使用方法引用来定义要进行排序的属性。

例子 16.使用类型安全的 API 定义排序表达式

TypedSort<Person> person = Sort.sort(Person.class);

TypedSort<Person> sort = person.by(Person::getFirstname).ascending()
  .and(person.by(Person::getLastname).descending());

如果您的 Store 实现支持 Querydsl,则还可以使用生成的元模型类型来定义排序表达式:

例子 17.使用 Querydsl API 定义排序表达式

QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

8.4.5. 限制查询结果

可以使用firsttop关键字来限制查询方法的结果,这些关键字可以互换使用。可以将一个可选的数值附加到topfirst以指定要返回的最大结果大小。如果省略该数字,则假定结果大小为 1.以下示例显示了如何限制查询大小:

例子 18.用TopFirst限制查询的结果大小

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表达式还支持Distinct关键字。此外,对于将结果集限制为一个实例的查询,支持使用Optional关键字将结果包装到其中。

如果将分页或切片应用于限制查询分页(以及对可用页面数的计算),则会在限制结果内应用分页或切片。

Note

通过使用Sort参数来限制结果与动态排序的组合,可让您表达针对最小'K'以及针对'K'最大元素的查询方法。

8.4.6. 存储库方法返回集合或可迭代对象

返回多个结果的查询方法可以使用标准 Java IterableListSet。除此之外,我们还支持返回 Spring Data 的Streamable,自定义 extensionsIterable以及Vavr提供的集合类型。

使用 Streamable 作为查询方法返回类型

Streamable可以替代Iterable或任何集合类型。它提供了方便的方法来访问非并行Stream(缺少Iterable),可以直接在元素上进行….filter(…)….map(…)并将Streamable连接到其他元素:

例子 19.使用 Streamable 合并查询方法结果

interface PersonRepository extends Repository<Person, Long> {
  Streamable<Person> findByFirstnameContaining(String firstname);
  Streamable<Person> findByLastnameContaining(String lastname);
}

Streamable<Person> result = repository.findByFirstnameContaining("av")
  .and(repository.findByLastnameContaining("ea"));
返回自定义流式包装器类型

为集合提供专用的包装器类型是一种常用的模式,用于在返回多个元素的查询执行结果上提供 API。通常,这些类型是通过调用存储库方法来返回类似集合的类型并手动创建包装类型的实例来使用的。如果 Spring Data 允许这些包装器类型满足以下条件,则可以避免使用这些包装器类型作为查询方法返回类型:

  • 该类型实现Streamable

  • 该类型公开以Streamable作为参数的名为of(…)valueOf(…)的构造函数或静态工厂方法。

示例用例如下所示:

class Product { (1)
  MonetaryAmount getPrice() { … }
}

@RequiredArgConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)

  private Streamable<Product> streamable;

  public MonetaryAmount getTotal() { (3)
    return streamable.stream() //
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }
}

interface ProductRepository implements Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text); (4)
}
  • (1) Product实体,公开 API 以访问产品价格。
  • (2) Streamable<Product>的包装类型可以通过Products.of(…)构造(通过 Lombok 注解创建的工厂方法)。
  • (3) 包装器类型在Streamable<Product>上公开了计算新值的其他 API。
  • (4) 可以将包装器类型直接用作查询方法返回类型。无需返回Stremable<Product>并将其手动包装在存储库 Client 端中。
支持 Vavr 集合

Vavr是一个包含 Java 中的函数式编程概念的库。它附带了一组自定义的收集类型,可用作查询方法返回类型。

Vavr 收集类型 使用的 Vavr 实现类型 有效的 Java 源代码类型
io.vavr.collection.Seq io.vavr.collection.List java.util.Iterable
io.vavr.collection.Set io.vavr.collection.LinkedHashSet java.util.Iterable
io.vavr.collection.Map io.vavr.collection.LinkedHashMap java.util.Map

第一列中的类型(或其子类型)可以用作查询方法返回类型,并将根据实际查询结果的 Java 类型(第三列)获取第二列中的类型作为实现类型。或者,可以声明Traversable(Vavr 等于Iterable),然后从实际的返回值派生实现类,即java.util.List将变成 Vavr List/Seqjava.util.Set变成 Vavr LinkedHashSet/Set等。

8.4.7. 存储库方法的空处理

从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8 的Optional表示可能没有值。除此之外,Spring Data 支持在查询方法上返回以下包装器类型:

  • com.google.common.base.Optional

  • scala.Option

  • io.vavr.control.Option

另外,查询方法可以选择根本不使用包装器类型。然后,通过返回null来指示是否缺少查询结果。保证返回集合,集合替代项,包装器和流的存储库方法永远不会返回null,而是会返回相应的空表示形式。有关详细信息,请参见“ Repositories 查询返回类型”。

Nullability Annotations

您可以使用Spring Framework 的可空性 Comments表示存储库方法的可空性约束。它们提供了一种对工具友好的方法,并在运行时选择了null检查,如下所示:

  • @NonNullApi:在包级别用于声明参数和返回值的默认行为是不接受或产生null值。

  • @NonNull:用于不得为null的参数或返回值(对于适用@NonNullApi的参数和返回值不需要)。

  • @Nullable:用于可以为null的参数或返回值。

SpringComments 使用JSR 305Comments(休眠但分布广泛的 JSR)进行元 Comments。 JSR 305 元 Comments 使IDEAEclipseKotlin之类的工具供应商以通用方式提供了空安全支持,而无需硬编码对 SpringComments 的支持。为了对查询方法的可空性约束进行运行时检查,您需要使用package-info.java中的 Spring 的@NonNullApi在包级别激活非空性,如以下示例所示:

示例 20.在package-info.java中声明不可为空

@org.springframework.lang.NonNullApi
package com.acme;

一旦设置了非 null 的默认值,就可以在运行时验证存储库查询方法的调用是否具有可空性约束。如果查询执行结果违反了定义的约束,则会引发异常。当该方法返回null但被声明为不可为空时(在存储库所在的包中定义了 Comments 的默认值),就会发生这种情况。如果您想再次选择接受可为空的结果,请在各个方法上有选择地使用@Nullable。使用本节开头提到的结果包装器类型可以按预期 continue 工作:将空结果转换为表示缺席的值。

以下示例显示了刚才描述的许多技术:

例子 21.使用不同的可空性约束

package com.acme;                                                       (1)

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);                    (2)

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);          (3)

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); (4)
}
  • (1) 存储库位于我们为其定义了非 null 行为的包(或子包)中。
  • (2) 当执行的查询未产生结果时,引发EmptyResultDataAccessException。当传递给该方法的emailAddressnull时抛出IllegalArgumentException
  • (3) 当执行的查询未产生结果时,返回null。还接受null作为emailAddress的值。
  • (4) 当执行的查询未产生结果时,返回Optional.empty()。当传递给该方法的emailAddressnull时抛出IllegalArgumentException
基于 Kotlin 的存储库中的可空性

Kotlin 定义了nullability constraints语言的定义。 Kotlin 代码编译为字节码,该字节码不通过方法签名来表达可空性约束,而是通过内置的元数据来表达。确保在您的项目中包含kotlin-reflect JAR 以启用对 Kotlin 可为空性约束的内省。 Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:

例子 22.在 Kotlin 仓库上使用可空性约束

interface UserRepository : Repository<User, String> {

  fun findByUsername(username: String): User     (1)

  fun findByFirstname(firstname: String?): User? (2)
}
  • (1) 该方法将参数和结果都定义为不可为空(Kotlin 默认值)。 Kotlin 编译器拒绝将null传递给方法的方法调用。如果查询执行产生空结果,则抛出EmptyResultDataAccessException
  • (2) 此方法接受null作为firstname参数,如果查询执行未产生结果,则返回null

8.4.8. 流查询结果

通过使用 Java 8 Stream<T>作为返回类型,可以递增地处理查询方法的结果。而不是将查询结果包装在Stream数据存储区中,而是使用特定于方法的方法来执行流传输,如以下示例所示:

例子 23.用 Java 8 Stream<T>流式查询的结果

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

Note

Stream可能包装了特定于底层数据存储的资源,因此在使用后必须将其关闭。您可以使用close()方法或使用 Java 7 try-with-resources块来手动关闭Stream,如以下示例所示:

例子 24.使用Stream<T>会导致 try-with-resources 块

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}

Note

目前,并非所有的 Spring Data 模块都支持Stream<T>作为返回类型。

8.4.9. 异步查询结果

可以使用Spring 的异步方法执行能力异步运行存储库查询。这意味着该方法在调用时立即返回,而实际查询执行发生在已提交给 Spring TaskExecutor的任务中。异步查询执行与反应式查询执行不同,因此不应混为一谈。有关响应式支持的更多详细信息,请参阅 Store 特定的文档。以下示例显示了许多异步查询:

@Async
Future<User> findByFirstname(String firstname);               (1)

@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)

@Async
ListenableFuture<User> findOneByLastname(String lastname);    (3)
  • (1) 使用java.util.concurrent.Future作为返回类型。
  • (2) 使用 Java 8 java.util.concurrent.CompletableFuture作为返回类型。
  • (3) 使用org.springframework.util.concurrent.ListenableFuture作为返回类型。

8.5. 创建存储库实例

在本部分中,将为已定义的存储库接口创建实例和 Bean 定义。一种方法是使用支持存储库机制的每个 Spring Data 模块随附的 Spring 名称空间,尽管我们通常建议使用 Java 配置。

8.5.1. XML 配置

每个 Spring Data 模块都包含一个repositories元素,可让您定义 Spring 会为您扫描的基本包,如以下示例所示:

例子 25.通过 XML 启用 Spring Data 仓库

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

  <repositories base-package="com.acme.repositories" />

</beans:beans>

在前面的示例中,指示 Spring 扫描com.acme.repositories及其所有子包,以查找扩展Repository或其接口之一的接口。对于找到的每个接口,基础结构都会注册特定于持久性技术的FactoryBean,以创建处理查询方法调用的适当代理。每个 bean 都使用从接口名称派生的 bean 名称进行注册,因此UserRepository的接口将注册在userRepository下。 base-package属性允许使用通配符,以便您可以定义扫描程序包的模式。

Using filters

默认情况下,基础结构会拾取扩展位于配置的基本程序包下的特定于持久性技术的Repository子接口的每个接口,并为其创建一个 bean 实例。但是,您可能希望更精细地控制哪些接口具有为其创建的 Bean 实例。为此,请在<repositories />元素内使用<include-filter /><exclude-filter />元素。语义完全等同于 Spring 的上下文名称空间中的元素。有关详细信息,请参见Spring 参考文档

例如,要将某些接口从实例中排除为存储库 Bean,可以使用以下配置:

例子 26.使用 exclude-filter 元素

<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

前面的示例排除了实例化以SomeRepository结尾的所有接口。

8.5.2. JavaConfig

还可以通过在 JavaConfig 类上使用 Store 特定的@Enable${store}RepositoriesComments 来触发存储库基础结构。有关 Spring 容器的基于 Java 的配置的介绍,请参见Spring 参考文档中的 JavaConfig

启用 Spring 数据存储库的示例配置类似于以下内容:

例子 27.基于 samplesComments 的存储库配置

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}

Note

前面的示例使用特定于 JPA 的 Comments,您将根据实际使用的 Store 模块对其进行更改。 EntityManagerFactory bean 的定义也是如此。请参阅有关 Store 特定配置的部分。

8.5.3. 独立使用

您还可以在 Spring 容器之外使用存储库基础结构,例如在 CDI 环境中。您的 Classpath 中仍然需要一些 Spring 库,但是,通常,您也可以通过编程方式来设置存储库。提供存储库支持的 Spring Data 模块附带了特定于持久性技术的RepositoryFactory,您可以按以下方式使用它们:

例子 28.仓库工厂的独立使用

RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

8.6. Spring 数据存储库的定制实现

本节介绍存储库定制以及片段如何形成复合存储库。

当查询方法需要不同的行为或无法通过查询派生实现时,则有必要提供自定义实现。 Spring Data 存储库使您可以提供自定义存储库代码,并将其与通用 CRUD 抽象和查询方法功能集成。

8.6.1. 自定义单个存储库

要使用自定义功能丰富存储库,必须首先定义片段接口和自定义功能的实现,如以下示例所示:

例子 29.定制仓库功能的接口

interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

然后,可以让您的存储库接口另外从片段接口扩展,如以下示例所示:

例子 30.定制仓库功能的实现

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

Note

与片段接口相对应的类名称中最重要的部分是Impl后缀。

实现本身不依赖于 Spring Data,可以是常规的 Spring bean。因此,您可以使用标准的依赖项注入行为来注入对其他 bean(例如JdbcTemplate)的引用,参与各个方面,等等。

您可以让您的存储库接口扩展片段接口,如以下示例所示:

示例 31.对您的存储库界面的更改

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

将片段接口扩展为您的存储库接口,将 CRUD 和自定义功能结合在一起,并使它可用于 Client 端。

Spring Data 存储库是通过使用构成存储库组成的片段来实现的。片段是基础存储库,功能方面(例如QueryDsl)以及自定义接口及其实现。每次向存储库接口添加接口时,都通过添加片段来增强组合。每个 Spring Data 模块都提供了基础存储库和存储库方面的实现。

以下示例显示了自定义接口及其实现:

例子 32.片段及其实现

interface HumanRepository {
  void someHumanMethod(User user);
}

class HumanRepositoryImpl implements HumanRepository {

  public void someHumanMethod(User user) {
    // Your custom implementation
  }
}

interface ContactRepository {

  void someContactMethod(User user);

  User anotherContactMethod(User user);
}

class ContactRepositoryImpl implements ContactRepository {

  public void someContactMethod(User user) {
    // Your custom implementation
  }

  public User anotherContactMethod(User user) {
    // Your custom implementation
  }
}

以下示例显示了扩展了CrudRepository的自定义存储库的界面:

例子 33.对您的存储库界面的更改

interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

  // Declare query methods here
}

存储库可能由多个自定义实现组成,这些自定义实现按其声明 Sequences 导入。定制实现比基础实现和存储库方面的优先级更高。通过此排序,可以覆盖基本存储库和方面方法,并在两个片段贡献相同方法签名的情况下解决歧义。存储库片段不限于在单个存储库界面中使用。多个存储库可以使用片段接口,使您可以跨不同的存储库重用自定义项。

以下示例显示了存储库片段及其实现:

例子 34.覆盖save(…)的片段

interface CustomizedSave<T> {
  <S extends T> S save(S entity);
}

class CustomizedSaveImpl<T> implements CustomizedSave<T> {

  public <S extends T> S save(S entity) {
    // Your custom implementation
  }
}

以下示例显示了使用上述存储库片段的存储库:

例子 35.定制的仓库接口

interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
Configuration

如果使用名称空间配置,则存储库基础结构会尝试通过扫描发现存储库的包下方的类来自动检测自定义实现片段。这些类需要遵循将命名空间元素的repository-impl-postfix属性附加到片段接口名称的命名约定。此后缀默认为Impl。以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:

例子 36.配置例子

<repositories base-package="com.acme.repository" />

<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />

上一个示例中的第一个配置尝试查找一个名为com.acme.repository.CustomizedUserRepositoryImpl的类以充当自定义存储库实现。第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix

解决歧义

如果在不同的包中找到具有匹配类名的多个实现,Spring Data 将使用 Bean 名称来标识要使用的那个。

给定前面显示的CustomizedUserRepository的以下两个自定义实现,将使用第一个实现。它的 bean 名称是customizedUserRepositoryImpl,与片段接口(CustomizedUserRepository)和后缀Impl匹配。

例子 37.歧义实现的解决

package com.acme.impl.one;

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}
package com.acme.impl.two;

@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}

如果用@Component("specialCustom")CommentsUserRepository接口,则 Bean 名称加Impl会匹配com.acme.impl.two中为存储库实现定义的名称,并且将使用它代替第一个。

Manual Wiring

如果您的自定义实现仅使用基于 Comments 的配置和自动装配,则上述显示的方法可以很好地工作,因为它被视为其他任何 Spring bean。如果实现片段 bean 需要特殊的接线,则可以声明 Bean 并根据preceding section中描述的约定对其进行命名。然后,基础结构通过名称引用手动定义的 bean 定义,而不是自己创建一个。以下示例显示如何手动连接自定义实现:

例子 38.手工连接定制实现

<repositories base-package="com.acme.repository" />

<beans:bean id="userRepositoryImpl" class="…">
  <!-- further configuration -->
</beans:bean>

8.6.2. 自定义基础存储库

当您要自定义基本存储库行为时,preceding section中描述的方法要求自定义每个存储库接口,以使所有存储库均受到影响。要改为更改所有存储库的行为,您可以创建一个实现,以扩展特定于持久性技术的存储库 Base Class。然后,该类充当存储库代理的自定义 Base Class,如以下示例所示:

例子 39.定制存储库 Base Class

class MyRepositoryImpl<T, ID>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}

Warning

该类需要具有特定于存储库的存储库工厂实现使用的超类的构造函数。如果存储库 Base Class 具有多个构造函数,则使用EntityInformation加上 Store 特定的基础结构对象(例如EntityManager或模板类)覆盖该构造函数。

最后一步是使 Spring Data 基础结构了解定制的存储库 Base Class。在 Java 配置中,您可以使用@Enable${store}Repositories注解的repositoryBaseClass属性来执行此操作,如以下示例所示:

例子 40.使用 JavaConfig 配置一个定制的仓库 Base Class

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

XML 名称空间中提供了相应的属性,如以下示例所示:

例子 41.使用 XML 配置一个定制的存储库 Base Class

<repositories base-package="com.acme.repository"
     base-class="….MyRepositoryImpl" />

8.7. 从汇总根发布事件

由存储库 Management 的实体是聚合根。在域驱动的设计应用程序中,这些聚合根通常发布域事件。 Spring Data 提供了一个名为@DomainEvents的 Comments,您可以在聚合根的方法上使用该 Comments,以使该发布尽可能容易,如以下示例所示:

例子 42.从聚合根公开域事件

class AnAggregateRoot {

    @DomainEvents (1)
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }

    @AfterDomainEventPublication (2)
    void callbackMethod() {
       // … potentially clean up domain events list
    }
}
  • (1) 使用@DomainEvents的方法可以返回单个事件实例或事件集合。它不能接受任何参数。
  • (2) 在发布所有事件之后,我们将使用@AfterDomainEventPublicationComments 方法。它可以用来潜在地清除要发布的事件列表(以及其他用途)。

每次调用 Spring Data 存储库的save(…)方法之一时,就会调用这些方法。

8.8. Spring 数据扩展

本节记录了一组 Spring Data 扩展,这些扩展允许在各种上下文中使用 Spring Data。当前,大多数集成都针对 Spring MVC。

8.8.1. Querydsl 扩展

Querydsl是一个框架,可通过其流畅的 API 构造静态类型的类似 SQL 的查询。

几个 Spring Data 模块通过QuerydslPredicateExecutor提供与 Querydsl 的集成,如以下示例所示:

例子 43. QuerydslPredicateExecutor 接口

public interface QuerydslPredicateExecutor<T> {

  Optional<T> findById(Predicate predicate);  (1)

  Iterable<T> findAll(Predicate predicate);   (2)

  long count(Predicate predicate);            (3)

  boolean exists(Predicate predicate);        (4)

  // … more functionality omitted.
}
  • (1) 查找并返回与Predicate匹配的单个实体。
  • (2) 查找并返回与Predicate匹配的所有实体。
  • (3) 返回与Predicate匹配的实体数。
  • (4) 返回与Predicate匹配的实体是否存在。

要使用 Querydsl 支持,请在存储库界面上扩展QuerydslPredicateExecutor,如以下示例所示

例子 44.存储库上的 Querydsl 集成

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

前面的示例使您可以使用 Querydsl Predicate实例编写类型安全查询,如以下示例所示:

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
	.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

8.8.2. 网路支援

Note

本部分包含 Spring Data Web 支持的文档,该文档在 Spring Data Commons 的当前(和更高版本)中实现。随着新引入的支持发生了许多变化,我们将以前的行为的文档保存在[web.legacy]中。

支持存储库编程模型的 Spring Data 模块附带各种 Web 支持。与 Web 相关的组件要求 Spring MVC JAR 位于 Classpath 上。其中一些甚至提供与Spring HATEOAS的集成。通常,通过使用 JavaConfig 配置类中的@EnableSpringDataWebSupportComments 来启用集成支持,如以下示例所示:

例子 45.启用 Spring Data Web 支持

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupportComments 注册了一些我们稍后将讨论的组件。它还将在 Classpath 上检测 Spring HATEOAS,并为其注册集成组件(如果存在)。

或者,如果您使用 XML 配置,则将SpringDataWebConfigurationHateoasAwareSpringDataWebConfiguration注册为 Spring Bean,如以下示例所示(针对SpringDataWebConfiguration):

例子 46.在 XML 中启用 Spring Data Web 支持

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本网络支持

previous section中显示的配置注册了一些基本组件:

DomainClassConverter

DomainClassConverter允许您直接在 Spring MVC 控制器方法签名中使用域类型,因此您无需通过存储库手动查找实例,如以下示例所示:

例子 47.一个在方法签名中使用域类型的 Spring MVC 控制器

@Controller
@RequestMapping("/users")
class UserController {

  @RequestMapping("/{id}")
  String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

如您所见,该方法直接接收User实例,不需要进一步的查找。可以通过让 Spring MVC 首先将路径变量转换为域类的id类型并最终通过在为该域类型注册的存储库实例上调用findById(…)来访问该实例来解决该实例。

Note

当前,存储库必须实现CrudRepository才能被发现以进行转换。

用于分页和排序的 HandlerMethodArgumentResolvers

previous section中显示的配置代码段还注册了PageableHandlerMethodArgumentResolver以及SortHandlerMethodArgumentResolver的实例。注册使PageableSort作为有效的控制器方法参数,如以下示例所示:

例子 48.使用 Pageable 作为控制器方法参数

@Controller
@RequestMapping("/users")
class UserController {

  private final UserRepository repository;

  UserController(UserRepository repository) {
    this.repository = repository;
  }

  @RequestMapping
  String showUsers(Model model, Pageable pageable) {

    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}

前面的方法签名使 Spring MVC 尝试使用以下默认配置从请求参数派生Pageable实例:

表 1.为Pageable个实例评估的请求参数

page 您要检索的页面。 0 索引,默认为 0.
size 您要检索的页面大小。默认为 20
sort 应该以property,property(,ASC|DESC)格式排序的属性。默认排序方向为升序。如果要切换方向,请使用多个sort参数,例如?sort=firstname&sort=lastname,asc

要自定义此行为,请注册分别实现PageableHandlerMethodArgumentResolverCustomizer接口或SortHandlerMethodArgumentResolverCustomizer接口的 bean。调用其customize()方法,可以更改设置,如以下示例所示:

@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
    return s -> s.setPropertyDelimiter("<-->");
}

如果设置现有MethodArgumentResolver的属性不足以满足您的目的,请扩展SpringDataWebConfiguration或启用 HATEOAS 的等效项,覆盖pageableResolver()sortResolver()方法,然后导入自定义的配置文件,而不使用@EnableComments。

如果您需要从请求中解析多个PageableSort实例(例如,对于多个表),则可以使用 Spring 的@QualifierComments 将一个实例与另一个实例区分开。然后,请求参数必须以${qualifier}_为前缀。以下示例显示了生成的方法签名:

String showUsers(Model model,
      @Qualifier("thing1") Pageable first,
      @Qualifier("thing2") Pageable second) { … }

您必须填充thing1_pagething2_page,依此类推。

传递给该方法的默认Pageable等效于PageRequest.of(0, 20),但可以使用Pageable参数上的@PageableDefaultComments 进行自定义。

超媒体对分页的支持

Spring HATEOAS 附带一个表示模型类(PagedResources),该类允许使用必要的Page元数据以及链接来丰富Page实例的内容,并使 Client 端可以轻松浏览页面。 Page 到PagedResources的转换是通过 Spring HATEOAS ResourceAssembler接口(称为PagedResourcesAssembler)的实现完成的。下面的示例演示如何使用PagedResourcesAssembler作为控制器方法参数:

例子 49.使用 PagedResourcesAssembler 作为控制器方法参数

@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

如上例所示,启用配置可以将PagedResourcesAssembler用作控制器方法参数。对其调用toResources(…)具有以下效果:

  • Page的内容成为PagedResources实例的内容。

  • PagedResources对象附加了PageMetadata实例,并使用Page和基础PageRequest的信息填充该实例。

  • PagedResources可能会附加prevnext链接,具体取决于页面的状态。链接指向方法 Map 到的 URI。添加到该方法的分页参数与PageableHandlerMethodArgumentResolver的设置匹配,以确保以后可以解析链接。

假设数据库中有 30 个 Person 实例。现在,您可以触发一个请求(GET http://localhost:8080/persons)并查看类似于以下内容的输出:

{ "links" : [ { "rel" : "next",
                "href" : "http://localhost:8080/persons?page=1&size=20 }
  ],
  "content" : [
     … // 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

您会看到汇编器生成了正确的 URI,并且还选择了默认配置以将参数解析为Pageable以应对即将到来的请求。这意味着,如果您更改该配置,则链接将自动遵循更改。默认情况下,汇编器指向调用它的控制器方法,但是可以通过传递自定义Link进行自定义,以将其用作构建分页链接的基础,这会使PagedResourcesAssembler.toResource(…)方法超载。

Web 数据绑定支持

通过使用JSONPath表达式(需要Jayway JsonPathXPath表达式(需要XmlBeam)),可以使用 Spring Data 投影(在Projections中描述)来绑定传入的请求有效负载,如以下示例所示:

例子 50.使用 JSONPath 或 XPath 表达式的 HTTP 有效负载绑定

@ProjectedPayload
public interface UserPayload {

  @XBRead("//firstname")
  @JsonPath("$..firstname")
  String getFirstname();

  @XBRead("/lastname")
  @JsonPath({ "$.lastname", "$.user.lastname" })
  String getLastname();
}

前面示例中显示的类型可以用作 Spring MVC 处理程序方法参数,也可以在RestTemplate的方法之一上使用ParameterizedTypeReference。前面的方法声明将尝试在给定文档中的任何位置找到firstnamelastname XML 查找在传入文档的顶层执行。的 JSON 变体首先尝试使用顶级lastname,但是如果前者未返回值,则还会尝试嵌套在user子文档中的lastname。这样,可以轻松缓解源文档结构的更改,而无需 Client 端调用公开的方法(通常是基于类的有效负载绑定的缺点)。

Projections中所述,支持嵌套投影。如果该方法返回复杂的非接口类型,则使用 Jackson ObjectMapperMap 最终值。

对于 Spring MVC,一旦@EnableSpringDataWebSupport处于活动状态,所需的转换器就会自动注册,并且所需的依赖项在 Classpath 上可用。要与RestTemplate结合使用,请手动注册ProjectingJackson2HttpMessageConverter(JSON)或XmlBeamHttpMessageConverter

有关更多信息,请参见规范Spring 数据示例存储库中的网络投影示例

Querydsl Web 支持

对于具有QueryDSL集成的 Store,可以从Request查询字符串中包含的属性派生查询。

考虑以下查询字符串:

?firstname=Dave&lastname=Matthews

给定前面示例中的User对象,可以使用QuerydslPredicateArgumentResolver将查询字符串解析为以下值。

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

Note

当在 Classpath 中找到 Querydsl 时,该功能将与@EnableSpringDataWebSupport一起自动启用。

在方法签名中添加@QuerydslPredicate即可使用的Predicate,可以使用QuerydslPredicateExecutor来运行。

Tip

类型信息通常从方法的返回类型中解析。由于该信息不一定与域类型匹配,因此最好使用QuerydslPredicateroot属性。

以下示例显示了如何在方法签名中使用@QuerydslPredicate

@Controller
class UserController {

  @Autowired UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    (1)
          Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

    model.addAttribute("users", repository.findAll(predicate, pageable));

    return "index";
  }
}
  • (1) 将查询字符串参数解析为与User匹配的Predicate

默认绑定如下:

  • Object用作eq的简单属性。

  • Object收集诸如contains之类的属性。

  • Collection用作in的简单属性。

可以通过@QuerydslPredicatebindings属性或通过使用 Java 8 default methods并将QuerydslBinderCustomizer方法添加到存储库接口来定制那些绑定。

interface UserRepository extends CrudRepository<User, String>,
                                 QuerydslPredicateExecutor<User>,                (1)
                                 QuerydslBinderCustomizer<QUser> {               (2)

  @Override
  default void customize(QuerydslBindings bindings, QUser user) {

    bindings.bind(user.username).first((path, value) -> path.contains(value))    (3)
    bindings.bind(String.class)
      .first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
    bindings.excluding(user.password);                                           (5)
  }
}
  • (1) QuerydslPredicateExecutor提供对Predicate的特定查找器方法的访问权限。
  • (2) 在存储库界面上定义的QuerydslBinderCustomizer会自动显示,快捷方式@QuerydslPredicate(bindings=…)
  • (3)username属性的绑定定义为简单的contains绑定。
  • (4)String属性的默认绑定定义为不区分大小写的contains匹配。
  • (5)Predicate分辨率中排除password属性。

8.8.3. 存储库填充器

如果您使用 Spring JDBC 模块,则可能熟悉使用 SQL 脚本填充DataSource的支持。尽管它不使用 SQL 作为数据定义语言,因为它必须独立于存储,因此在存储库级别上可以使用类似的抽象。因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。

假设您有一个具有以下内容的文件data.json

例子 51.用 JSON 定义的数据

[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

您可以使用 Spring Data Commons 中提供的存储库名称空间的 populator 元素来填充存储库。要将前面的数据填充到您的 PersonRepository 中,请声明一个类似于以下内容的填充器:

例子 52.声明一个 Jackson 存储库填充器

<?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:repository="http://www.springframework.org/schema/data/repository"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    https://www.springframework.org/schema/data/repository/spring-repository.xsd">

  <repository:jackson2-populator locations="classpath:data.json" />

</beans>

前面的声明使 Jackson ObjectMapper读取并反序列化data.json文件。

通过检查 JSON 文档的_class属性来确定将 JSON 对象解组到的类型。基础结构最终选择适当的存储库来处理反序列化的对象。

要改为使用 XML 定义应使用存储库填充的数据,可以使用unmarshaller-populator元素。您可以将其配置为使用 Spring OXM 中可用的 XML marshaller 选项之一。有关详情,请参见Spring 参考文档。以下示例显示如何使用 JAXB 解组存储库填充器:

例子 53.声明一个解组存储库填充器(使用 JAXB)

<?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:repository="http://www.springframework.org/schema/data/repository"
  xmlns:oxm="http://www.springframework.org/schema/oxm"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    https://www.springframework.org/schema/data/repository/spring-repository.xsd
    http://www.springframework.org/schema/oxm
    https://www.springframework.org/schema/oxm/spring-oxm.xsd">

  <repository:unmarshaller-populator locations="classpath:data.json"
    unmarshaller-ref="unmarshaller" />

  <oxm:jaxb2-marshaller contextPath="com.acme" />

</beans>

Reference Documentation

9. Introduction

9 .1.文件结构

参考文档的这一部分说明了 Spring Data MongoDB 提供的核心功能。

MongoDB support”介绍了 MongoDB 模块功能集。

MongoDB Repositories”介绍了对 MongoDB 的存储库支持。

10. MongoDB 支持

MongoDB 支持包含广泛的功能:

  • Spring 配置支持基于 Java 的@Configuration类或 Mongo 驱动程序实例和副本集的 XML 名称空间。

  • MongoTemplate帮助器类,可以在执行常见的 Mongo 操作时提高工作效率。包括文档和 POJO 之间的集成对象 Map。

  • 将异常转换为 Spring 的可移植数据访问异常层次结构。

  • 与 Spring 的转换服务集成的功能丰富的对象 Map。

  • 基于 Comments 的 Map 元数据,可扩展以支持其他元数据格式。

  • 持久性和 Map 生命周期事件。

  • 基于 Java 的查询,条件和更新 DSL。

  • 存储库接口的自动实现,包括对自定义查找器方法的支持。

  • QueryDSL 集成以支持类型安全的查询。

  • 对 JPA 实体的跨 Store 持久性支持,其中字段透明地持久化并通过 MongoDB 检索(不建议使用-无需替换即可删除)。

  • GeoSpatial integration.

对于大多数任务,您应该使用MongoTemplate或存储库支持,这两者都利用了丰富的 Map 功能。 MongoTemplate是寻找访问功能(例如递增计数器或临时 CRUD 操作)的地方。 MongoTemplate还提供了回调方法,以便您轻松获得com.mongodb.client.MongoDatabase等低级 API 工件以直接与 MongoDB 通信。各种 API 工件上的命名约定的目标是将这些复制到基本的 MongoDB Java 驱动程序中,以便您可以轻松地将现有知识 Map 到 Spring API 上。

10.1. 入门

引导设置工作环境的一种简单方法是在STS中创建一个基于 Spring 的项目。

首先,您需要设置一个正在运行的 MongoDB 服务器。有关如何启动 MongoDB 实例的说明,请参阅MongoDB 快速入门指南。安装后,启动 MongoDB 通常只需运行以下命令:${MONGO_HOME}/bin/mongod

在 STS 中创建 Spring 项目:

  • 转到文件→新建→Spring 模板项目→Simple Spring Utility 项目,然后在出现提示时按 Yes。然后 Importing 项目和程序包名称,例如org.spring.mongodb.example。将以下内容添加到 pom.xml 文件dependencies元素中:
<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>2.2.1.RELEASE</version>
  </dependency>

</dependencies>
  • 在 pom.xml 中将 Spring 的版本更改为
<spring.framework.version>5.2.1.RELEASE</spring.framework.version>
  • 将 Maven 的 Spring Milestone 存储库的以下位置添加到pom.xml中,使其与<dependencies/>元素处于同一级别:
<repositories>
  <repository>
    <id>spring-milestone</id>
    <name>Spring Maven MILESTONE Repository</name>
    <url>https://repo.spring.io/libs-milestone</url>
  </repository>
</repositories>

该存储库也是browseable here

您可能还希望将日志记录级别设置为DEBUG,以查看一些其他信息。为此,请编辑log4j.properties文件以具有以下内容:

log4j.category.org.springframework.data.mongodb=DEBUG
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n

然后,您可以创建一个Person类来持久化:

package org.spring.mongodb.example;

public class Person {

  private String id;
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  }
}

您还需要一个主应用程序来运行:

package org.spring.mongodb.example;

import static org.springframework.data.mongodb.core.query.Criteria.where;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;

import com.mongodb.client.MongoClients;

public class MongoApp {

  private static final Log log = LogFactory.getLog(MongoApp.class);

  public static void main(String[] args) throws Exception {

    MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "database");
    mongoOps.insert(new Person("Joe", 34));

    log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));

    mongoOps.dropCollection("person");
  }
}

当您运行主程序时,以上示例将产生以下输出:

10:01:32,062 DEBUG apping.MongoPersistentEntityIndexCreator:  80 - Analyzing class class org.spring.example.Person for index information.
10:01:32,265 DEBUG ramework.data.mongodb.core.MongoTemplate: 631 - insert Document containing fields: [_class, age, name] in collection: Person
10:01:32,765 DEBUG ramework.data.mongodb.core.MongoTemplate:1243 - findOne using query: { "name" : "Joe"} in db.collection: database.Person
10:01:32,953  INFO      org.spring.mongodb.example.MongoApp:  25 - Person [id=4ddbba3c0be56b7e1b210166, name=Joe, age=34]
10:01:32,984 DEBUG ramework.data.mongodb.core.MongoTemplate: 375 - Dropped collection [database.person]

即使在这个简单的示例中,也没有什么要注意的:

  • 您可以使用标准的com.mongodb.MongoClient对象和要使用的数据库名称来实例化 Spring Mongo 的中央帮助程序类MongoTemplate

  • Map 器可用于标准 POJO 对象,而无需任何其他元数据(尽管您可以选择提供该信息.请参见here。)。

  • 约定用于处理id字段,将其存储在数据库中时将其转换为ObjectId

  • Map 约定可以使用字段访问。请注意,Person类只有吸气剂。

  • 如果构造函数参数名称与存储文档的字段名称匹配,则将它们用于实例化对象

10.2. 示例库

您可以下载一个GitHub 存储库,包含几个示例并进行试用,以了解该库的工作方式。

10.3. 使用 Spring 连接到 MongoDB

使用 MongoDB 和 Spring 时,首要任务之一是使用 IoC 容器创建com.mongodb.MongoClientcom.mongodb.client.MongoClient对象。使用基于 Java 的 Bean 元数据或使用基于 XML 的 Bean 元数据有两种主要方法。在以下各节中将讨论这两者。

Note

对于不熟悉如何使用基于 Java 的 Bean 元数据而不是基于 XML 的元数据配置 Spring 容器的用户,请参阅参考文档here和详细文档here中的高级介绍。

10.3.1. 使用基于 Java 的元数据注册 Mongo 实例

以下示例显示了使用基于 Java 的 Bean 元数据注册com.mongodb.MongoClient实例的示例:

例子 54.使用基于 Java 的 bean 元数据注册一个com.mongodb.MongoClient对象

@Configuration
public class AppConfig {

  /*
   * Use the standard Mongo driver API to create a com.mongodb.MongoClient instance.
   */
   public @Bean MongoClient mongoClient() {
       return new MongoClient("localhost");
   }
}

这种方法使您可以使用标准com.mongodb.MongoClient实例,而容器使用 Spring 的MongoClientFactoryBean。与直接实例化com.mongodb.MongoClient实例相比,FactoryBean还具有为容器提供ExceptionTranslator实现的附加优点,该实现将 MongoDB 异常转换为 Spring 便携式DataAccessException层次结构中针对带有@RepositoryComments 的数据访问类的异常。 Spring 的 DAO 支持功能中描述了此层次结构和@Repository的用法。

以下示例显示了一个基于 Java 的 Bean 元数据的示例,该元数据支持对@Repository个带 Comments 的类的异常转换:

例子 55.使用 Spring 的 MongoClientFactoryBean 注册com.mongodb.MongoClient对象并启用 Spring 的异常转换支持

@Configuration
public class AppConfig {

    /*
     * Factory bean that creates the com.mongodb.MongoClient instance
     */
     public @Bean MongoClientFactoryBean mongo() {
          MongoClientFactoryBean mongo = new MongoClientFactoryBean();
          mongo.setHost("localhost");
          return mongo;
     }
}

要访问其他@Configuration类或您自己的类中MongoClientFactoryBean创建的com.mongodb.MongoClient对象,请使用private @Autowired Mongo mongo;字段。

10.3.2. 使用基于 XML 的元数据注册 Mongo 实例

尽管您可以使用 Spring 的传统<beans/> XML 名称空间向容器注册com.mongodb.MongoClient的实例,但 XML 可能非常冗长,因为它是通用的。 XML 名称空间是配置常用对象(如 Mongo 实例)的更好的选择。 mongo 命名空间使您可以创建 Mongo 实例服务器的位置,副本集和选项。

要使用 Mongo 名称空间元素,您需要引用 Mongo 模式,如下所示:

例子 56.用于配置 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.xsd
          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.xsd">

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

</beans>

以下示例显示了使用MongoClientOptions的更高级的配置(请注意,这些不是推荐值):

例子 57. XML 模式使用 MongoClientOptions 配置 com.mongodb.MongoClient 对象

<beans>

  <mongo:mongo-client host="localhost" port="27017">
    <mongo:client-options connections-per-host="8"
                   threads-allowed-to-block-for-connection-multiplier="4"
                   connect-timeout="1000"
                   max-wait-time="1500}"
                   auto-connect-retry="true"
                   socket-keep-alive="true"
                   socket-timeout="1500"
                   slave-ok="true"
                   write-number="1"
                   write-timeout="0"
                   write-fsync="true"/>
  </mongo:mongo-client>

</beans>

以下示例显示了使用副本集的配置:

例子 58.使用副本集配置com.mongodb.MongoClient对象的 XML 模式

<mongo:mongo-client id="replicaSetMongo" replica-set="127.0.0.1:27017,localhost:27018"/>

10.3.3. MongoDbFactory 接口

com.mongodb.MongoClient是 MongoDB 驱动程序 API 的入口点,而连接到特定的 MongoDB 数据库实例则需要其他信息,例如数据库名称以及可选的用户名和密码。有了这些信息,您可以获得com.mongodb.client.MongoDatabase对象并访问特定 MongoDB 数据库实例的所有功能。 Spring 提供了org.springframework.data.mongodb.core.MongoDbFactory接口,如下面的清单所示,用于引导与数据库的连接:

public interface MongoDbFactory {

  MongoDatabase getDb() throws DataAccessException;

  MongoDatabase getDb(String dbName) throws DataAccessException;
}

以下各节说明如何将容器与基于 Java 或基于 XML 的元数据一起使用,以配置MongoDbFactory接口的实例。反过来,您可以使用MongoDbFactory实例配置MongoTemplate

您可以在标准 Java 代码中使用它们,而不用使用 IoC 容器创建 MongoTemplate 的实例,如下所示:

public class MongoApp {

  private static final Log log = LogFactory.getLog(MongoApp.class);

  public static void main(String[] args) throws Exception {

    MongoOperations mongoOps = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "database"));

    mongoOps.insert(new Person("Joe", 34));

    log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));

    mongoOps.dropCollection("person");
  }
}

粗体代码突出显示了SimpleMongoDbFactory的用法,并且是入门部分中显示的清单之间的唯一区别。

Note

选择com.mongodb.client.MongoClient作为选择的入口点时,请使用SimpleMongoClientDbFactory

10.3.4. 使用基于 Java 的元数据注册 MongoDbFactory 实例

要在容器中注册MongoDbFactory实例,请编写代码,就像上一个代码清单中突出显示的代码一样。以下清单显示了一个简单的示例:

@Configuration
public class MongoConfiguration {

  public @Bean MongoDbFactory mongoDbFactory() {
    return new SimpleMongoDbFactory(new MongoClient(), "database");
  }
}

连接到数据库时,MongoDB Server 第三代更改了身份验证模型。因此,某些可用于身份验证的配置选项不再有效。您应使用特定于MongoClient的选项来通过MongoCredential设置凭据以提供身份验证数据,如以下示例所示:

@Configuration
public class ApplicationContextEventTestsAppConfig extends AbstractMongoConfiguration {

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

  @Override
  @Bean
  public MongoClient mongoClient() {
    return new MongoClient(singletonList(new ServerAddress("127.0.0.1", 27017)),
      singletonList(MongoCredential.createCredential("name", "db", "pwd".toCharArray())));
  }
}

为了对基于 XML 的配置使用身份验证,请在<mongo-client>元素上使用credentials属性。

Note

当基于 XML 的配置中使用的用户名和密码凭据包含保留字符(例如:%@,)时,必须进行 URL 编码。以下示例显示了编码的凭据:[email protected]:mo_res:bw6},[email protected]@databasem0ng0%40dmin:mo_res%3Abw6%7D%2CQsdxx%[email protected]有关更多详细信息,请参见RFC 3986 的 2.2 节

从 MongoDB Java 驱动程序 3.7.0 开始,通过mongodb-driver-sync构件向MongoClient提供了一个替代入口点。 com.mongodb.client.MongoClientcom.mongodb.MongoClient不兼容,并且不再支持旧版DBObject编解码器。因此,它不能与Querydsl一起使用,并且需要不同的配置。您可以使用AbstractMongoClientConfiguration来利用新的MongoClients构建器 API。

@Configuration
public class MongoClientConfiguration extends AbstractMongoClientConfiguration {

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

	@Override
	public MongoClient mongoClient() {
		return MongoClients.create("mongodb://localhost:27017/?replicaSet=rs0&w=majority");
	}
}

10.3.5. 使用基于 XML 的元数据注册 MongoDbFactory 实例

与使用<beans/>命名空间相比,mongo命名空间提供了一种方便的方式来创建SimpleMongoDbFactory,如以下示例所示:

<mongo:db-factory dbname="database">

如果需要在用于创建SimpleMongoDbFactorycom.mongodb.MongoClient实例上配置其他选项,则可以使用mongo-ref属性引用现有的 bean,如以下示例所示。为了显示另一种常见用法模式,下面的清单显示了属性占位符的用法,它使您可以参数化配置和MongoTemplate的创建:

<context:property-placeholder location="classpath:/com/myapp/mongodb/config/mongo.properties"/>

<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
  <mongo:client-options
     connections-per-host="${mongo.connectionsPerHost}"
     threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
     connect-timeout="${mongo.connectTimeout}"
     max-wait-time="${mongo.maxWaitTime}"
     auto-connect-retry="${mongo.autoConnectRetry}"
     socket-keep-alive="${mongo.socketKeepAlive}"
     socket-timeout="${mongo.socketTimeout}"
     slave-ok="${mongo.slaveOk}"
     write-number="1"
     write-timeout="0"
     write-fsync="true"/>
</mongo:mongo-client>

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

<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>

10.4. MongoTemplate 简介

MongoTemplate类位于org.springframework.data.mongodb.core包中,是 Spring 对 MongoDB 支持的中心类,并提供了丰富的功能集来与数据库进行交互。该模板提供了创建,更新,删除和查询 MongoDB 文档的便捷操作,并提供了域对象和 MongoDB 文档之间的 Map。

Note

配置完成后,MongoTemplate是线程安全的,并且可以在多个实例之间重用。

MongoDB 文档和域类之间的 Map 是通过委派MongoConverter接口的实现来完成的。 Spring 提供了MappingMongoConverter,但是您也可以编写自己的转换器。有关更多详细信息,请参见“ 自定义转化-覆盖默认 Map”。

MongoTemplate类实现接口MongoOperationsMongoOperations上的方法尽可能以 MongoDB 驱动程序Collection对象上可用的方法命名,以使熟悉该驱动程序 API 的现有 MongoDB 开发人员熟悉该 API。例如,您可以找到findfindAndModifyfindAndReplacefindOneinsertremovesaveupdateupdateMulti之类的方法。设计目标是使在基本 MongoDB 驱动程序和MongoOperations的使用之间的转换尽可能容易。两种 API 之间的主要区别在于,可以将MongoOperations而不是Document传递给域对象。同样,MongoOperations具有针对QueryCriteriaUpdate操作的流畅的 API,而不是填充Document为这些操作指定参数。

Note

引用MongoTemplate实例上的操作的首选方法是通过其接口MongoOperations

MongoTemplate使用的默认转换器实现是MappingMongoConverterMappingMongoConverter可以使用其他元数据来指定对象到文档的 Map,但是它也可以通过使用一些 ID 和集合名称的 Map 约定来转换不包含其他元数据的对象。这些约定以及 MapComments 的使用在“ Mapping”一章中进行了说明。

MongoTemplate的另一个主要功能是将 MongoDB Java 驱动程序引发的异常转换为 Spring 的可移植数据访问异常层次结构。有关更多信息,请参见“ Exception Translation”。

MongoTemplate提供了许多便利的方法来帮助您轻松执行常见任务。但是,如果您需要直接访问 MongoDB 驱动程序 API,则可以使用几种Execute回调方法之一。 execute 回调为您提供了对com.mongodb.client.MongoCollectioncom.mongodb.client.MongoDatabase对象的引用。有关更多信息,请参见"Execution Callbacks"部分。

下一部分包含一个如何在 Spring 容器的上下文中使用MongoTemplate的示例。

10.4.1. 实例化 MongoTemplate

您可以使用 Java 创建并注册MongoTemplate的实例,如以下示例所示:

例子 59.注册一个com.mongodb.MongoClient对象并启用 Spring 的异常转换支持

@Configuration
public class AppConfig {

  public @Bean MongoClient mongoClient() {
      return new MongoClient("localhost");
  }

  public @Bean MongoTemplate mongoTemplate() {
      return new MongoTemplate(mongoClient(), "mydatabase");
  }
}

MongoTemplate有几个重载的构造函数:

  • MongoTemplate(MongoClient mongo, String databaseName):使用MongoClient对象和默认数据库名称进行操作。

  • MongoTemplate(MongoDbFactory mongoDbFactory):采用一个 MongoDbFactory 对象,该对象封装了MongoClient对象,数据库名称以及用户名和密码。

  • MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter):添加一个MongoConverter用于 Map。

您还可以使用 Spring 的 XML<beans/>模式配置 MongoTemplate,如以下示例所示:

<mongo:mongo-client host="localhost" port="27017"/>

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg ref="mongoClient"/>
  <constructor-arg name="databaseName" value="geospatial"/>
</bean>

创建MongoTemplate时可能要设置的其他可选属性是默认的WriteResultCheckingPolicyWriteConcernReadPreference属性。

Note

引用MongoTemplate实例上的操作的首选方法是通过其接口MongoOperations

10.4.2. WriteResultChecking 策略

在开发中,如果从任何 MongoDB 操作返回的com.mongodb.WriteResult包含错误,则可以方便地记录或引发异常。通常很容易忘记在开发过程中执行此操作,然后最终得到一个看起来运行成功的应用程序,而实际上该数据库并未根据您的期望进行修改。您可以将MongoTemplateWriteResultChecking属性设置为以下值之一:EXCEPTIONNONE,分别抛出Exception或不执行任何操作。默认值为NONEWriteResultChecking值。

10.4.3. WriteConcern

如果尚未通过更高级别的驱动程序(例如com.mongodb.MongoClient)指定它,则可以设置MongoTemplate用于写操作的com.mongodb.WriteConcern属性。如果未设置WriteConcern属性,则默认为 MongoDB 驱动程序的 DB 或 Collection 设置中的一个。

10.4.4. WriteConcernResolver

对于更高级的情况,您希望基于每个操作设置不同的WriteConcern值(用于删除,更新,插入和保存操作),可以在MongoTemplate上配置名为WriteConcernResolver的策略接口。由于MongoTemplate用于持久化 POJO,因此WriteConcernResolver使您可以创建可将特定 POJO 类 Map 到WriteConcern值的策略。以下清单显示了WriteConcernResolver接口:

public interface WriteConcernResolver {
  WriteConcern resolve(MongoAction action);
}

您可以使用MongoAction参数来确定WriteConcern值,也可以使用模板本身的值作为默认值。 MongoAction包含要写入的集合名称,POJO 的java.lang.Class,转换后的Document,操作(REMOVEUPDATEINSERTINSERT_LISTSAVE)以及其他一些上下文信息。下面的示例显示了两组具有不同的WriteConcern设置的类:

private class MyAppWriteConcernResolver implements WriteConcernResolver {

  public WriteConcern resolve(MongoAction action) {
    if (action.getEntityClass().getSimpleName().contains("Audit")) {
      return WriteConcern.NONE;
    } else if (action.getEntityClass().getSimpleName().contains("Metadata")) {
      return WriteConcern.JOURNAL_SAFE;
    }
    return action.getDefaultWriteConcern();
  }
}

10.5. 保存,更新和删除文档

MongoTemplate使您可以保存,更新和删除域对象,并将这些对象 Map 到 MongoDB 中存储的文档。

考虑以下类别:

public class Person {

  private String id;
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  }

}

给定上一示例中的Person类,您可以保存,更新和删除该对象,如以下示例所示:

Note

MongoOperationsMongoTemplate实现的接口。

package org.spring.example;

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Update.update;
import static org.springframework.data.mongodb.core.query.Query.query;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;

import com.mongodb.client.MongoClients;

public class MongoApp {

  private static final Log log = LogFactory.getLog(MongoApp.class);

  public static void main(String[] args) {

    MongoOperations mongoOps = new MongoTemplate(new SimpleMongoClientDbFactory(MongoClients.create(), "database"));

    Person p = new Person("Joe", 34);

    // Insert is used to initially store the object into the database.
    mongoOps.insert(p);
    log.info("Insert: " + p);

    // Find
    p = mongoOps.findById(p.getId(), Person.class);
    log.info("Found: " + p);

    // Update
    mongoOps.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class);
    p = mongoOps.findOne(query(where("name").is("Joe")), Person.class);
    log.info("Updated: " + p);

    // Delete
    mongoOps.remove(p);

    // Check that deletion worked
    List<Person> people =  mongoOps.findAll(Person.class);
    log.info("Number of people = : " + people.size());

    mongoOps.dropCollection(Person.class);
  }
}

前面的示例将产生以下日志输出(包括来自MongoTemplate的调试消息):

DEBUG apping.MongoPersistentEntityIndexCreator:  80 - Analyzing class class org.spring.example.Person for index information.
DEBUG work.data.mongodb.core.MongoTemplate: 632 - insert Document containing fields: [_class, age, name] in collection: person
INFO               org.spring.example.MongoApp:  30 - Insert: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=34]
DEBUG work.data.mongodb.core.MongoTemplate:1246 - findOne using query: { "_id" : { "$oid" : "4ddc6e784ce5b1eba3ceaf5c"}} in db.collection: database.person
INFO               org.spring.example.MongoApp:  34 - Found: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=34]
DEBUG work.data.mongodb.core.MongoTemplate: 778 - calling update using query: { "name" : "Joe"} and update: { "$set" : { "age" : 35}} in collection: person
DEBUG work.data.mongodb.core.MongoTemplate:1246 - findOne using query: { "name" : "Joe"} in db.collection: database.person
INFO               org.spring.example.MongoApp:  39 - Updated: Person [id=4ddc6e784ce5b1eba3ceaf5c, name=Joe, age=35]
DEBUG work.data.mongodb.core.MongoTemplate: 823 - remove using query: { "id" : "4ddc6e784ce5b1eba3ceaf5c"} in collection: person
INFO               org.spring.example.MongoApp:  46 - Number of people = : 0
DEBUG work.data.mongodb.core.MongoTemplate: 376 - Dropped collection [database.person]

MongoConverter通过(通过约定)识别Id属性名称,导致在数据库中存储的StringObjectId之间进行隐式转换。

Note

前面的示例旨在显示对MongoTemplate的保存,更新和删除操作的使用,而不显示复杂的 Map 功能。

在前面的示例中使用的查询语法在“ Querying Documents”部分中进行了详细说明。

10.5.1. _id 字段在 Map 层中的处理方式

MongoDB 要求所有文档都具有一个_id字段。如果不提供,驱动程序将为ObjectId分配一个生成的值。当使用MappingMongoConverter时,某些规则控制 Java 类的属性如何 Map 到此_id字段:

  • @Id(org.springframework.data.annotation.Id)Comments 的属性或字段 Map 到_id字段。

  • 没有 Comments 但名为id的属性或字段 Map 到_id字段。

下面概述了使用MappingMongoConverter(MongoTemplate的默认值)时,对 Map 到_id document 字段的属性进行的类型转换(如果有)。

  • 如果可能,通过使用 Spring Converter<String, ObjectId>将 Java 类中声明为Stringid属性或字段转换为ObjectId并存储为ObjectId。有效的转换规则委托给 MongoDB Java 驱动程序。如果不能将其转换为ObjectId,那么该值将作为字符串存储在数据库中。

  • 使用 Spring Converter<BigInteger, ObjectId>将 Java 类中声明为BigIntegerid属性或字段转换为ObjectId并存储为ObjectId

如果 Java 类中不存在先前规则集中指定的字段或属性,则驱动程序将生成一个隐式_id文件,但不会将其 Map 到 Java 类的属性或字段。

在查询和更新时,MongoTemplate使用与前面的规则相对应的转换器来保存文档,以便查询中使用的字段名称和类型可以与域类中的内容匹配。

某些环境需要一种自定义方法来 MapId值,例如未通过 Spring 数据 Map 层运行的 MongoDB 中存储的数据。文档可以包含_id个值,这些值可以表示为ObjectIdString。从 Store 中将文档读回域类型就可以了。由于ObjectId的隐式转换,通过id查询文档可能很麻烦。因此,无法以这种方式检索文档。对于这些情况,@MongoId提供了对实际 IDMap 尝试的更多控制。

例子 60. @MongoIdMap

public class PlainStringId {
  @MongoId String id; (1)
}

public class PlainObjectId {
  @MongoId ObjectId id; (2)
}

public class StringToObjectId {
  @MongoId(FieldType.OBJECT_ID) String id; (3)
}
  • (1) 该 ID 被视为String,无需进一步转换。
  • (2) 该 ID 被视为ObjectId
  • (3) 如果给定的String是有效的ObjectId hex,则 ID 会被视为ObjectId,否则将被视为String。对应于@Id用法。

10.5.2. 类型 Map

MongoDB 集合可以包含代表各种类型实例的文档。如果您存储类的层次结构或具有一个类型为Object的属性的类,则此功能很有用。在后一种情况下,检索对象时必须正确读取该属性内保存的值。因此,我们需要一种将类型信息存储在实际文档旁边的机制。

为此,MappingMongoConverter使用MongoTypeMapper抽象并将DefaultMongoTypeMapper作为其主要实现。它的默认行为是将完全限定的类名存储在文档中的_class下。类型提示是为顶级文档以及每个值(如果它是复杂类型和已声明属性类型的子类型)编写的。以下示例(末尾带有 JSON 表示形式)显示了 Map 的工作方式:

例子 61.类型 Map

public class Sample {
  Contact value;
}

public abstract class Contact { … }

public class Person extends Contact { … }

Sample sample = new Sample();
sample.value = new Person();

mongoTemplate.save(sample);

{
  "value" : { "_class" : "com.acme.Person" },
  "_class" : "com.acme.Sample"
}

Spring Data MongoDB 将类型信息存储为实际根类以及嵌套类型的最后一个字段(因为它很复杂,并且是Contact的子类型)。因此,如果现在使用mongoTemplate.findAll(Object.class, "sample"),则可以发现存储的文档是Sample实例。您还可以发现 value 属性实际上是Person

自定义类型 Map

如果要避免将整个 Java 类名称写为类型信息,而是想使用键,则可以在实体类上使用@TypeAlias注解。如果您需要进一步自定义 Map,请查看TypeInformationMapper界面。可以在DefaultMongoTypeMapper处配置该接口的实例,而该实例又可以在MappingMongoConverter上进行配置。以下示例显示如何为实体定义类型别名:

例子 62.为实体定义类型别名

@TypeAlias("pers")
class Person {

}

请注意,生成的文档包含pers作为_class字段中的值。

配置自定义类型 Map

以下示例显示了如何在MappingMongoConverter中配置自定义MongoTypeMapper

例子 63.使用 Spring Java Config 配置一个定制的MongoTypeMapper

class CustomMongoTypeMapper extends DefaultMongoTypeMapper {
  //implement custom type mapping here
}
@Configuration
class SampleMongoConfiguration extends AbstractMongoConfiguration {

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

  @Override
  public MongoClient mongoClient() {
    return new MongoClient();
  }

  @Bean
  @Override
  public MappingMongoConverter mappingMongoConverter() throws Exception {
    MappingMongoConverter mmc = super.mappingMongoConverter();
    mmc.setTypeMapper(customTypeMapper());
    return mmc;
  }

  @Bean
  public MongoTypeMapper customTypeMapper() {
    return new CustomMongoTypeMapper();
  }
}

请注意,前面的示例扩展了AbstractMongoConfiguration类,并覆盖了配置自定义MongoTypeMapperMappingMongoConverter的 bean 定义。

以下示例显示如何使用 XML 配置自定义MongoTypeMapper

例子 64.用 XML 配置一个自定义MongoTypeMapper

<mongo:mapping-converter type-mapper-ref="customMongoTypeMapper"/>

<bean name="customMongoTypeMapper" class="com.bubu.mongo.CustomMongoTypeMapper"/>

10.5.3. 保存和插入文件的方法

MongoTemplate上有几种方便的方法可以保存和插入对象。要对转换过程进行更细粒度的控制,可以在MappingMongoConverter中注册 Spring 转换器,例如Converter<Person, Document>Converter<Document, Person>

Note

插入和保存操作之间的区别在于,如果对象尚不存在,则保存操作将执行插入操作。

使用保存操作的简单情况是保存 POJO。在这种情况下,集合名称由类的名称(不完全限定)确定。您也可以使用特定的集合名称调用保存操作。您可以使用 Map 元数据覆盖存储对象的集合。

插入或保存时,如果未设置Id属性,则假定其值将由数据库自动生成。因此,要成功自动生成ObjectId,类中的Id属性或字段的类型必须为StringObjectIdBigInteger

下面的示例显示如何保存文档并检索其内容:

例子 65.使用 MongoTemplate 插入和检索文档

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Criteria.query;
…

Person p = new Person("Bob", 33);
mongoTemplate.insert(p);

Person qp = mongoTemplate.findOne(query(where("age").is(33)), Person.class);

可以使用以下插入和保存操作:

  • void 保存 (Object objectToSave):将对象保存到默认集合。

  • void 保存 (Object objectToSave, String collectionName):将对象保存到指定的集合。

也可以使用一组类似的插入操作:

  • void 插入 (Object objectToSave):将对象插入默认集合。

  • void 插入 (Object objectToSave, String collectionName):将对象插入指定的集合。

我的文档保存到哪个集合中?

有两种方法来 Management 用于文档的集合名称。使用的默认集合名称是更改为以小写字母开头的类名称。因此com.test.Person类存储在person集合中。您可以通过提供带有@Document注解的其他集合名称来自定义此名称。您还可以通过提供自己的集合名称作为所选MongoTemplate方法调用的最后一个参数来覆盖集合名称。

插入或保存单个对象

MongoDB 驱动程序支持在单个操作中插入文档集合。 MongoOperations界面中的以下方法支持此功能:

  • 插入 :插入一个对象。如果存在具有相同id的现有文档,则会生成错误。

  • insertAll :将对象的Collection作为第一个参数。此方法根据先前指定的规则检查每个对象并将其插入到适当的集合中。

  • 保存 :保存对象,覆盖任何可能具有相同id的对象。

批量插入多个对象

MongoDB 驱动程序支持在一个操作中插入一组文档。 MongoOperations界面中的以下方法支持此功能:

  • 插入 方法:以Collection作为第一个参数。他们在单次写入数据库中插入对象列表。

10.5.4. 更新集合中的文档

对于更新,您可以使用MongoOperation.updateFirst更新找到的第一个文档,或者可以使用MongoOperation.updateMulti方法更新找到与查询匹配的所有文档。以下示例显示了所有SAVINGS帐户的更新,在这些帐户中,我们将使用$inc运算符向余额中添加一次性的$ 50.00 奖金:

例子 66.使用MongoTemplate更新文档

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Update;

...

WriteResult wr = mongoTemplate.updateMulti(new Query(where("accounts.accountType").is(Account.Type.SAVINGS)),
  new Update().inc("accounts.$.balance", 50.00), Account.class);

除了前面讨论的Query之外,我们还使用Update对象提供更新定义。 Update类具有与 MongoDB 可用的更新修饰符匹配的方法。

大多数方法都返回Update对象以为 API 提供流畅的样式。

执行文档更新的方法
  • updateFirst :使用更新的文档更新与查询文档条件匹配的第一个文档。

  • updateMulti :使用更新的文档更新所有与查询文档条件匹配的对象。

Warning

updateFirst不支持 Order。请使用findAndModify申请Sort

Update 类中的方法

您可以在Update类中使用一些“语法糖”,因为它的方法是要链接在一起的。另外,您可以使用public static Update update(String key, Object value)并使用静态导入来开始创建新的Update实例。

Update类包含以下方法:

  • Update addToSet (String key, Object value)使用$addToSet更新修饰符进行更新

  • Update currentDate (String key)使用$currentDate更新修饰符进行更新

  • Update currentTimestamp (String key)使用$currentDate update 修饰符和$type timestamp进行更新

  • Update inc (String key, Number inc)使用$inc更新修饰符进行更新

  • Update 最大 (String key, Object max)使用$max更新修饰符进行更新

  • Update 分钟 (String key, Object min)使用$min更新修饰符进行更新

  • Update 相乘 (String key, Number multiplier)使用$mul更新修饰符进行更新

  • Update 流行 (String key, Update.Position pos)使用$pop更新修饰符进行更新

  • Update (String key, Object value)使用$pull更新修饰符进行更新

  • Update pullAll (String key, Object[] values)使用$pullAll更新修饰符进行更新

  • Update 推送 (String key, Object value)使用$push更新修饰符进行更新

  • Update pushAll (String key, Object[] values)使用$pushAll更新修饰符进行更新

  • Update 重命名 (String oldName, String newName)使用$rename更新修饰符进行更新

  • Update 设置 (String key, Object value)使用$set更新修饰符进行更新

  • Update setOnInsert (String key, Object value)使用$setOnInsert更新修饰符进行更新

  • Update 未设置 (String key)使用$unset更新修饰符进行更新

一些更新修饰符(例如$push$addToSet)允许嵌套其他运算符。

// { $push : { "category" : { "$each" : [ "spring" , "data" ] } } }
new Update().push("category").each("spring", "data")

// { $push : { "key" : { "$position" : 0 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel"));

// { $push : { "key" : { "$slice" : 5 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel"));

// { $addToSet : { "values" : { "$each" : [ "spring" , "data" , "mongodb" ] } } }
new Update().addToSet("values").each("spring", "data", "mongodb");

10.5.5. 集合中的“更新”文档

与执行updateFirst操作有关,您还可以执行“ upsert”操作,如果找不到与查询匹配的文档,则将执行插入操作。插入的文档是查询文档和更新文档的组合。下面的示例演示如何使用upsert方法:

template.upsert(query(where("ssn").is(1111).and("firstName").is("Joe").and("Fraizer").is("Update")), update("address", addr), Person.class);

Warning

upsert不支持 Order。请使用findAndModify申请Sort

10.5.6. 查找和上载集合中的文档

MongoCollection上的findAndModify(…)方法可以更新文档并通过一次操作返回旧的或新更新的文档。 MongoTemplate提供了四个findAndModify重载方法,这些方法采用QueryUpdate类并将Document转换为您的 POJO:

<T> T findAndModify(Query query, Update update, Class<T> entityClass);

<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);

<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);

<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass, String collectionName);

下面的示例将一些Person对象插入容器并执行findAndUpdate操作:

mongoTemplate.insert(new Person("Tom", 21));
mongoTemplate.insert(new Person("Dick", 22));
mongoTemplate.insert(new Person("Harry", 23));

Query query = new Query(Criteria.where("firstName").is("Harry"));
Update update = new Update().inc("age", 1);
Person p = mongoTemplate.findAndModify(query, update, Person.class); // return's old person object

assertThat(p.getFirstName(), is("Harry"));
assertThat(p.getAge(), is(23));
p = mongoTemplate.findOne(query, Person.class);
assertThat(p.getAge(), is(24));

// Now return the newly updated document when updating
p = template.findAndModify(query, update, new FindAndModifyOptions().returnNew(true), Person.class);
assertThat(p.getAge(), is(25));

FindAndModifyOptions方法可让您设置returnNewupsertremove的选项。从前面的代码片段扩展来的示例如下:

Query query2 = new Query(Criteria.where("firstName").is("Mary"));
p = mongoTemplate.findAndModify(query2, update, new FindAndModifyOptions().returnNew(true).upsert(true), Person.class);
assertThat(p.getFirstName(), is("Mary"));
assertThat(p.getAge(), is(1));

10.5.7. 查找和替换文档

替换整个Document的最直接方法是使用save方法通过其id。但是,这可能并不总是可行的。 findAndReplace提供了一种替代方法,它允许通过简单的查询来标识要替换的文档。

例子 67.查找和替换文件

Optional<User> result = template.update(Person.class)      (1)
    .matching(query(where("firstame").is("Tom")))          (2)
    .replaceWith(new Person("Dick"))
    .withOptions(FindAndReplaceOptions.options().upsert()) (3)
    .as(User.class)                                        (4)
    .findAndReplace();                                     (5)
  • (1) 使用具有指定域类型的 Fluent 更新 APIMap 查询和导出集合名称,或者只使用MongoOperations#findAndReplace
  • (2) 针对给定域类型 Map 的实际匹配查询。通过查询提供sortfieldscollation设置。
  • (3) 附加的可选钩子,可提供默认值以外的选项,例如upsert
  • (4) 用于 Map 操作结果的可选投影类型。如果没有给出,则使用初始域类型。
  • (5) 触发实际执行。使用findAndReplaceValue而不是Optional获得可为空的结果。

Tip

请注意,替换商品一定不能拥有id本身,因为现有Documentid将由 Store 本身转移到替换商品中。还请记住,findAndReplace只会根据可能给定的排序 Sequences 替换匹配查询条件的第一个文档。

10.5.8. 删除文件的方法

您可以使用以下五种重载方法之一从数据库中删除对象:

template.remove(tywin, "GOT");                                              (1)

template.remove(query(where("lastname").is("lannister")), "GOT");           (2)

template.remove(new Query().limit(3), "GOT");                               (3)

template.findAllAndRemove(query(where("lastname").is("lannister"), "GOT");  (4)

template.findAllAndRemove(new Query().limit(3), "GOT");                     (5)
  • (1) 从关联的集合中删除由其_id指定的单个实体。
  • (2)GOT集合中删除所有符合查询条件的文档。
  • (3) 删除GOT集合中的前三个文档。与\ <2>不同,要删除的文档由它们的_id标识,执行给定查询,首先应用sortlimitskip选项,然后在单独的步骤中一次删除所有文档。
  • (4)GOT集合中删除所有符合查询条件的文档。与\ <3>不同,不会批量删除文档,而是逐个删除文档。
  • (5) 删除GOT集合中的前三个文档。与\ <3>不同,不会批量删除文档,而是逐个删除文档。

10.5.9. 乐观锁

@VersionComments 提供的语法类似于 MongoDB 上下文中的 JPA 语法,并确保更新仅适用于具有匹配版本的文档。因此,将 version 属性的实际值添加到更新查询中,使得如果与此同时其他操作更改了文档,则更新不会产生任何影响。在这种情况下,将抛出OptimisticLockingFailureException。以下示例显示了这些功能:

@Document
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));                            (1)

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); (2)

daenerys.setLastname("Targaryen");
template.save(daenerys);                                                              (3)

template.save(tmp); // throws OptimisticLockingFailureException                       (4)
  • (1) 最初插入文档。 version设置为0
  • (2) 加载刚刚插入的文档。 version仍然是0
  • (3)version = 0更新文档。将lastname设置为1,然后将version设置为1
  • (4) 尝试更新仍具有version = 0的先前加载的文档。由于当前version1,因此操作失败并显示OptimisticLockingFailureException

Tip

乐观锁定要求将WriteConcern设置为ACKNOWLEDGED。否则OptimisticLockingFailureException可以被静默吞下。

Note

从 2.2 版开始,从数据库中删除实体时,MongoOperations还包括@Version属性。要删除不带版本的Document,请使用MongoOperations#remove(Query,…)而不是MongoOperations#remove(Object)

Note

从版本 2.2 开始,存储库在删除版本化实体时检查确认删除的结果。如果无法通过CrudRepository.delete(Object)删除版本控制的实体,则会引发OptimisticLockingFailureException。在这种情况下,同时会更改版本或删除对象。使用CrudRepository.deleteById(ID)绕开乐观锁定功能并删除对象,无论其版本如何。

10.6. 查询文件

您可以使用QueryCriteria类来表达您的查询。它们具有与本地 MongoDB 运算符名称类似的方法名称,例如ltlteis等。 QueryCriteria类遵循 Fluent 的 API 样式,因此您可以将多个方法条件和查询链接在一起,同时拥有易于理解的代码。为了提高可读性,静态导入使您避免使用'new'关键字来创建QueryCriteria实例。您还可以使用BasicQuery从纯 JSON 字符串创建Query实例,如以下示例所示:

例子 68.从一个普通的 JSON 字符串创建一个查询实例

BasicQuery query = new BasicQuery("{ age : { $lt : 50 }, accounts.balance : { $gt : 1000.00 }}");
List<Person> result = mongoTemplate.find(query, Person.class);

Spring MongoDB 还支持 GeoSpatial 查询(请参见GeoSpatial Queries部分)和 Map-Reduce 操作(请参见Map-Reduce部分)。

10.6.1. 查询集合中的文档

之前,我们看到了如何使用MongoTemplate上的findOnefindById方法来检索单个文档。这些方法返回单个域对象。我们还可以查询要作为域对象列表返回的文档集合。假设我们有多个Person对象,它们的名称和年龄作为文档存储在集合中,并且每个人都有一个带有余额的嵌入式帐户文档,我们现在可以使用以下代码运行查询:

例子 69.使用 MongoTemplate 查询文件

import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

…

List<Person> result = mongoTemplate.find(query(where("age").lt(50)
  .and("accounts.balance").gt(1000.00d)), Person.class);

所有查找方法均以Query对象作为参数。该对象定义用于执行查询的条件和选项。通过使用具有静态工厂方法whereCriteria对象来指定标准,以实例化新的Criteria对象。我们建议对org.springframework.data.mongodb.core.query.Criteria.whereQuery.query使用静态导入,以使查询更具可读性。

该查询应返回满足指定条件的Person个对象的列表。本节的其余部分列出了CriteriaQuery类的方法,这些方法与 MongoDB 中提供的运算符相对应。大多数方法返回Criteria对象,以提供 API 的流畅样式。

条件类的方法

Criteria类提供以下方法,所有这些方法都对应于 MongoDB 中的运算符:

  • Criteria 全部 (Object o)使用$all运算符创建条件

  • Criteria (String key)将具有指定key的链接的Criteria添加到当前Criteria并返回新创建的

  • Criteria andOperator (Criteria… criteria)使用$and运算符创建并查询所有提供的条件(需要 MongoDB 2.0 或更高版本)

  • Criteria elemMatch (Criteria c)使用$elemMatch运算符创建条件

  • Criteria 存在 (boolean b)使用$exists运算符创建条件

  • Criteria gt (Object o)使用$gt运算符创建条件

  • Criteria gte (Object o)使用$gte运算符创建条件

  • Criteria in (Object… o)使用$in运算符为 varargs 参数创建条件。

  • Criteria Importing (Collection<?> collection)使用$in运算符使用集合创建条件

  • Criteria (Object o)使用字段匹配({ key:value })创建条件。如果指定的值是文档,则字段的 Sequences 和文档中的完全相等很重要。

  • Criteria lt (Object o)使用$lt运算符创建条件

  • Criteria lte (Object o)使用$lte运算符创建条件

  • Criteria mod (Number value, Number remainder)使用$mod运算符创建条件

  • Criteria (Object o)使用$ne运算符创建条件

  • Criteria nin (Object… o)使用$nin运算符创建条件

  • Criteria norOperator (Criteria… criteria)使用$nor运算符为所有提供的条件创建一个 nor 查询

  • Criteria ()使用$not元运算符创建条件,该条件会直接影响后面的子句

  • Criteria orOperator (Criteria… criteria)使用$or运算符为提供的所有条件创建一个或查询

  • Criteria regex (String re)使用$regex创建条件

  • Criteria 大小 (int s)使用$size运算符创建条件

  • Criteria 类型 (int t)使用$type运算符创建条件

  • Criteria matchingDocumentStructure (MongoJsonSchema schema)使用$jsonSchema运算符为JSON 模式条件创建条件。 $jsonSchema只能应用于查询的顶层,而不能应用于特定属性。使用架构的properties属性与嵌套字段进行匹配。

  • Criteria bits()MongoDB 按位查询运算符的网关,例如$bitsAllClear

Criteria 类还为地理空间查询提供了以下方法(请参阅GeoSpatial Queries部分以查看实际操作):

  • Criteria 内部 (Circle circle)使用$geoWithin $center运算符创建地理空间标准。

  • Criteria **内部(Box box)使用$geoWithin $box操作创建地理空间标准。

  • Criteria withinSphere (Circle circle)使用$geoWithin $center运算符创建地理空间标准。

  • Criteria 附近 (Point point)使用$near操作创建地理空间标准

  • Criteria nearSphere (Point point)使用$nearSphere$center操作创建地理空间标准。仅适用于 MongoDB 1.7 及更高版本。

  • Criteria minDistance (double minDistance)使用$minDistance操作创建地理空间标准,以与$ near 一起使用。

  • Criteria maxDistance (double maxDistance)使用$maxDistance操作创建地理空间标准,以与$ near 一起使用。

Query 类的方法

Query类具有一些其他方法,可为查询提供选项:

  • Query addCriteria (Criteria criteria)用于向查询添加其他条件

  • Field 字段 ()用于定义要包含在查询结果中的字段

  • Query limit (int limit)用于将返回结果的大小限制为提供的限制(用于分页)

  • Query 跳过 (int skip)用于跳过结果中提供的文档数量(用于分页)

  • Query 带有 (Sort sort)用于为结果提供排序定义

10.6.2. 查询文件的方法

查询方法需要指定返回的目标类型T,并且对于应对除返回类型指示的集合以外的其他集合进行操作的查询,它们会使用显式集合名称重载。以下查询方法使您可以查找一个或多个文档:

  • findAll **:从集合中查询类型为T的对象的列表。

  • findOne **:将集合上的即席查询结果 Map 到指定类型的对象的单个实例。

  • findById **:返回给定 ID 和目标类的对象。

  • 查找 :将对集合的即席查询结果 Map 到指定类型的List

  • findAndRemove:将对集合的即席查询结果 Map 到指定类型的对象的单个实例。返回与查询匹配的第一个文档,并将其从数据库的集合中删除。

10.6.3. 查询不同的值

MongoDB 提供了一种操作,可以通过使用对所得文档的查询来获取单个字段的不同值。结果值不需要具有相同的数据类型,功能也不限于简单类型。为了进行检索,实际结果类型对于转换和键入很重要。以下示例显示如何查询不同的值:

例子 70.检索不同的值

template.query(Person.class)  (1)
  .distinct("lastname")       (2)
  .all();                     (3)
  • (1) 查询Person集合。
  • (2) 选择lastname字段的不同值。根据域类型属性声明 Map 字段名称,同时考虑可能的@FieldComments。
  • (3)ListObject的形式检索所有不同的值(由于未指定明确的结果类型)。

将不同的值检索到ObjectCollection是最灵活的方法,因为它尝试确定域类型的属性值并将结果转换为所需的类型或 MapDocument结构。

有时,当所需字段的所有值都固定为某种类型时,直接获得正确键入的Collection更为方便,如以下示例所示:

例子 71.检索强类型的不同值

template.query(Person.class)  (1)
  .distinct("lastname")       (2)
  .as(String.class)           (3)
  .all();                     (4)
  • (1) 查询Person的集合。
  • (2) 选择lastname字段的不同值。字段名称根据域类型属性声明进行 Map,并考虑了可能的@FieldComments。
  • (3) 检索到的值将转换为所需的目标类型,在这种情况下为String。如果存储的字段包含文档,则也可以将值 Map 到更复杂的类型。
  • (4)StringList检索所有不同的值。如果类型不能转换为所需的目标类型,则此方法抛出DataAccessException

10.6.4. 地理空间查询

MongoDB 通过使用诸如$near$withingeoWithin$nearSphere之类的运算符来支持 GeoSpatial 查询。 Criteria类提供了特定于地理空间查询的方法。还有一些形状类(BoxCirclePoint)与地理空间相关的Criteria方法结合使用。

Note

在 MongoDB 事务中使用 GeoSpatial 查询时需要引起注意,请参阅Transaction 中的特殊行为

要了解如何执行 GeoSpatial 查询,请考虑以下Venue类(来自集成测试,并依赖于丰富的MappingMongoConverter):

@Document(collection="newyork")
public class Venue {

  @Id
  private String id;
  private String name;
  private double[] location;

  @PersistenceConstructor
  Venue(String name, double[] location) {
    super();
    this.name = name;
    this.location = location;
  }

  public Venue(String name, double x, double y) {
    super();
    this.name = name;
    this.location = new double[] { x, y };
  }

  public String getName() {
    return name;
  }

  public double[] getLocation() {
    return location;
  }

  @Override
  public String toString() {
    return "Venue [id=" + id + ", name=" + name + ", location="
        + Arrays.toString(location) + "]";
  }
}

要在Circle内查找位置,可以使用以下查询:

Circle circle = new Circle(-73.99171, 40.738868, 0.01);
List<Venue> venues =
    template.find(new Query(Criteria.where("location").within(circle)), Venue.class);

要使用球坐标找到Circle内的场地,可以使用以下查询:

Circle circle = new Circle(-73.99171, 40.738868, 0.003712240453784);
List<Venue> venues =
    template.find(new Query(Criteria.where("location").withinSphere(circle)), Venue.class);

要在Box内查找场所,可以使用以下查询:

//lower-left then upper-right
Box box = new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404));
List<Venue> venues =
    template.find(new Query(Criteria.where("location").within(box)), Venue.class);

要查找Point附近的场所,可以使用以下查询:

Point point = new Point(-73.99171, 40.738868);
List<Venue> venues =
    template.find(new Query(Criteria.where("location").near(point).maxDistance(0.01)), Venue.class);
Point point = new Point(-73.99171, 40.738868);
List<Venue> venues =
    template.find(new Query(Criteria.where("location").near(point).minDistance(0.01).maxDistance(100)), Venue.class);

要使用球坐标找到Point附近的场所,可以使用以下查询:

Point point = new Point(-73.99171, 40.738868);
List<Venue> venues =
    template.find(new Query(
        Criteria.where("location").nearSphere(point).maxDistance(0.003712240453784)),
        Venue.class);
Geo-near Queries

Warning

在 2.2 中更改!
MongoDB 4.2删除了对以前用于运行NearQuerygeoNear命令的支持。

Spring Data MongoDB 2.2 MongoOperations#geoNear使用$geoNear aggregation而不是geoNear命令来运行NearQuery

现在,以前在包装器类型内返回的计算距离(使用 geoNear 命令时为dis)现在已嵌入到生成的文档中。如果给定的域类型已经包含具有该名称的属性,则计算出的距离将被命名为calculated-distance且可能带有随机后缀。

目标类型可能包含一个以返回距离命名的属性,以(另外)将其直接读回域类型,如下所示。

GeoResults<VenueWithDisField> = template.query(Venue.class) (1)
.as(VenueWithDisField.class)                            (2)
.near(NearQuery.near(new GeoJsonPoint(-73.99, 40.73), KILOMETERS))
.all();
  • (1) 用来标识目标集合和潜在查询 Map 的域类型。
  • (2) 包含Number类型的dis字段的目标类型。

MongoDB 支持在数据库中查询地理位置,并同时计算到给定起点的距离。使用地理附近的查询,您可以表达查询,例如“查找周围 10 英里内的所有餐馆”。为此,MongoOperations提供了以NearQuery作为参数的geoNear(…)方法(以及已经熟悉的实体类型和集合),如以下示例所示:

Point location = new Point(-73.99171, 40.738868);
NearQuery query = NearQuery.near(location).maxDistance(new Distance(10, Metrics.MILES));

GeoResults<Restaurant> = operations.geoNear(query, Restaurant.class);

我们使用NearQuery builder API 设置查询,以将给定Point周围的所有Restaurant实例返回 10 英里。此处使用的Metrics枚举实际上实现了一个接口,因此其他 Metrics 也可以插入距离中。 Metric由乘法器支持,以将给定度量的距离值转换为原始距离。此处显示的示例会将 10 视为英里。使用内置 Metrics 之一(英里和公里)会自动触发要在查询中设置的球形标志。如果要避免这种情况,请将普通的double值传递给maxDistance(…)。有关更多信息,请参见NearQueryDistanceJavaDoc

geo-earear 操作返回一个GeoResults包装器对象,该对象封装了GeoResult个实例。包装GeoResults可以访问所有结果的平均距离。单个GeoResult对象携带找到的实体及其距原点的距离。

10.6.5. GeoJSON 支持

MongoDB 支持GeoJSON和简单(旧式)坐标对的地理空间数据。这些格式可用于存储和查询数据。请参阅MongoDB 有关 GeoJSON 支持的手册以了解要求和限制。

域类中的 GeoJSON 类型

域类中GeoJSON类型的用法很简单。 org.springframework.data.mongodb.core.geo软件包包含GeoJsonPointGeoJsonPolygon等类型。这些类型扩展了现有的org.springframework.data.geo类型。以下示例使用GeoJsonPoint

public class Store {

	String id;

	/**
	 * location is stored in GeoJSON format.
	 * {
	 *   "type" : "Point",
	 *   "coordinates" : [ x, y ]
	 * }
	 */
	GeoJsonPoint location;
}
存储库查询方法中的 GeoJSON 类型

将 GeoJSON 类型用作存储库查询参数会在创建查询时强制使用$geometry运算符,如以下示例所示:

public interface StoreRepository extends CrudRepository<Store, String> {

	List<Store> findByLocationWithin(Polygon polygon);  (1)

}

/*
 * {
 *   "location": {
 *     "$geoWithin": {
 *       "$geometry": {
 *         "type": "Polygon",
 *         "coordinates": [
 *           [
 *             [-73.992514,40.758934],
 *             [-73.961138,40.760348],
 *             [-73.991658,40.730006],
 *             [-73.992514,40.758934]
 *           ]
 *         ]
 *       }
 *     }
 *   }
 * }
 */
repo.findByLocationWithin(                              (2)
  new GeoJsonPolygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006),
    new Point(-73.992514, 40.758934)));                 (3)

/*
 * {
 *   "location" : {
 *     "$geoWithin" : {
 *        "$polygon" : [ [-73.992514,40.758934] , [-73.961138,40.760348] , [-73.991658,40.730006] ]
 *     }
 *   }
 * }
 */
repo.findByLocationWithin(                              (4)
  new Polygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006));
  • (1) 使用 commons 类型的存储库方法定义允许同时使用 GeoJSON 和传统格式进行调用。
  • (2) 使用 GeoJSON 类型来使用$geometry运算符。
  • (3) 请注意,GeoJSON 多边形需要定义一个闭合环。
  • (4) 使用旧格式$polygon运算符。
度量和距离计算

然后,MongoDB $geoNear运算符允许使用 GeoJSON Point 或旧版坐标对。

NearQuery.near(new Point(-73.99171, 40.738868))
{
  "$geoNear": {
    //...
    "near": [-73.99171, 40.738868]
  }
}
NearQuery.near(new GeoJsonPoint(-73.99171, 40.738868))
{
  "$geoNear": {
    //...
    "near": { "type": "Point", "coordinates": [-73.99171, 40.738868] }
  }
}

尽管在语法上有所不同,但是无论集合中的目标文档使用哪种格式,服务器都可以接受两者。

Warning

距离计算存在巨大差异。使用传统格式可在类似地球的球体上对弧度进行操作,而 GeoJSON 格式使用

为避免严重的头痛,请确保将Metric设置为所需的度量单位,以确保正确计算距离。

换一种说法:

假设您有 5 个文档,如下所示:

{
    "_id" : ObjectId("5c10f3735d38908db52796a5"),
    "name" : "Penn Station",
    "location" : { "type" : "Point", "coordinates" : [  -73.99408, 40.75057 ] }
}
{
    "_id" : ObjectId("5c10f3735d38908db52796a6"),
    "name" : "10gen Office",
    "location" : { "type" : "Point", "coordinates" : [ -73.99171, 40.738868 ] }
}
{
    "_id" : ObjectId("5c10f3735d38908db52796a9"),
    "name" : "City Bakery ",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
}
{
    "_id" : ObjectId("5c10f3735d38908db52796aa"),
    "name" : "Splash Bar",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
}
{
    "_id" : ObjectId("5c10f3735d38908db52796ab"),
    "name" : "Momofuku Milk Bar",
    "location" : { "type" : "Point", "coordinates" : [ -73.985839, 40.731698 ] }
}

使用 GeoJSON 从[-73.99171, 40.738868]提取半径 400 米以内的所有文档,如下所示:

例子 72.带有 GeoJSON 的 GeoNear

{
    "$geoNear": {
        "maxDistance": 400, (1)
        "num": 10,
        "near": { type: "Point", coordinates: [-73.99171, 40.738868] },
        "spherical":true, (2)
        "key": "location",
        "distanceField": "distance"
    }
}

返回以下 3 个文档:

{
    "_id" : ObjectId("5c10f3735d38908db52796a6"),
    "name" : "10gen Office",
    "location" : { "type" : "Point", "coordinates" : [ -73.99171, 40.738868 ] }
    "distance" : 0.0 (3)
}
{
    "_id" : ObjectId("5c10f3735d38908db52796a9"),
    "name" : "City Bakery ",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
    "distance" : 69.3582262492474 (3)
}
{
    "_id" : ObjectId("5c10f3735d38908db52796aa"),
    "name" : "Splash Bar",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
    "distance" : 69.3582262492474 (3)
}
  • (1)中距中心点的最大距离。
  • (2) GeoJSON 始终在球体上运行。
  • (3)中距中心点的距离。

现在,当使用旧式坐标对时,如前所述,将对弧度进行运算。因此我们使用Metrics#KILOMETERS when constructing the `$geoNear命令。 Metric确保距离乘数设置正确。

例子 73.具有传统坐标对的 GeoNear

{
    "$geoNear": {
        "maxDistance": 0.0000627142377, (1)
        "distanceMultiplier": 6378.137, (2)
        "num": 10,
        "near": [-73.99171, 40.738868],
        "spherical":true, (3)
        "key": "location",
        "distanceField": "distance"
    }
}

像 GeoJSON 变体一样返回 3 个文档:

{
    "_id" : ObjectId("5c10f3735d38908db52796a6"),
    "name" : "10gen Office",
    "location" : { "type" : "Point", "coordinates" : [ -73.99171, 40.738868 ] }
    "distance" : 0.0 (4)
}
{
    "_id" : ObjectId("5c10f3735d38908db52796a9"),
    "name" : "City Bakery ",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
    "distance" : 0.0693586286032982 (4)
}
{
    "_id" : ObjectId("5c10f3735d38908db52796aa"),
    "name" : "Splash Bar",
    "location" : { "type" : "Point", "coordinates" : [ -73.992491, 40.738673 ] }
    "distance" : 0.0693586286032982 (4)
}
  • (1)弧度的中心点的最大距离。
  • (2) 距离乘数,因此我们得到公里作为结果距离。
  • (3) 确保我们对 2d_sphere 索引进行操作。
  • (4)公里的中心点的距离-乘以 1000 即可匹配 GeoJSON 变体的

10.6.6. 全文查询

从 MongoDB 2.6 版开始,您可以使用$text运算符运行全文查询。 TextQueryTextCriteria中提供了针对全文查询的方法和操作。进行全文搜索时,请参见MongoDB reference的行为和限制。

在实际使用全文搜索之前,必须正确设置搜索索引。有关如何创建索引结构的更多详细信息,请参见Text Index。以下示例显示了如何设置全文本搜索:

db.foo.createIndex(
{
  title : "text",
  content : "text"
},
{
  weights : {
              title : 3
            }
}
)

可以按以下方式定义和执行查询coffee cake的查询,该查询按相关性按weights进行排序:

Query query = TextQuery.searching(new TextCriteria().matchingAny("coffee", "cake")).sortByScore();
List<Document> page = template.find(query, Document.class);

可以通过在搜索词前加上-或使用notMatching来排除搜索词,如以下示例所示(请注意,这两行效果相同,因此是多余的):

// search for 'coffee' and not 'cake'
TextQuery.searching(new TextCriteria().matching("coffee").matching("-cake"));
TextQuery.searching(new TextCriteria().matching("coffee").notMatching("cake"));

TextCriteria.matching照原样提供所提供的术语。因此,可以通过将短语放在双引号之间来定义短语(例如\"coffee cake\")TextCriteria.phrase.来使用)。以下示例显示了两种定义短语的方法:

// search for phrase 'coffee cake'
TextQuery.searching(new TextCriteria().matching("\"coffee cake\""));
TextQuery.searching(new TextCriteria().phrase("coffee cake"));

您可以使用TextCriteria上的相应方法为$caseSensitive$diacriticSensitive设置标志。请注意,这两个可选标志已在 MongoDB 3.2 中引入,除非明确设置,否则不会包含在查询中。

10.6.7. Collations

从 3.4 版开始,MongoDB 支持排序规则以进行收集和索引创建以及各种查询操作。归类基于ICU collations定义字符串比较规则。归类文档由封装在Collation中的各种属性组成,如以下清单所示:

Collation collation = Collation.of("fr")         (1)

  .strength(ComparisonLevel.secondary()          (2)
    .includeCase())

  .numericOrderingEnabled()                      (3)

  .alternate(Alternate.shifted().punct())        (4)

  .forwardDiacriticSort()                        (5)

  .normalizationEnabled();                       (6)
  • (1) Collation需要使用语言环境进行创建。这可以是语言环境的字符串表示形式,Locale(考虑语言,国家和地区)或CollationLocale。语言环境对于创建是必需的。
  • (2) 整理强度定义了表示字符之间差异的比较级别。您可以根据所选强度配置各种选项(区分大小写,区分大小写和其他)。
  • (3) 指定是将数字字符串比较为数字还是字符串。
  • (4) 指定归类是否应将空格和标点符号视为基本字符以进行比较。
  • (5) 指定带有变音符号的字符串是否从字符串的后面排序,例如使用某些法语词典排序。
  • (6) 指定是否检查文本是否需要规范化以及是否执行规范化。

排序规则可用于创建集合和索引。如果创建指定排序规则的集合,则除非指定其他排序规则,否则该排序规则将应用于索引创建和查询。排序规则对整个操作有效,不能在每个字段中指定。

像其他元数据一样,归类可以通过@Document注解的collation属性从域类型派生而来,并将在执行查询,创建集合或索引时直接应用。

Note

当 MongoDB 在首次交互时自动创建集合时,将不使用带 Comments 的排序规则。这将需要其他 Store 交互来延迟整个过程。在这种情况下,请使用MongoOperations.createCollection

Collation french = Collation.of("fr");
Collation german = Collation.of("de");

template.createCollection(Person.class, CollectionOptions.just(collation));

template.indexOps(Person.class).ensureIndex(new Index("name", Direction.ASC).collation(german));

Note

如果未指定排序规则(Collation.simple()),则 MongoDB 使用简单的二进制比较。

将归类与收集操作一起使用,只需在查询或操作选项中指定一个Collation实例,如以下两个示例所示:

例子 74.对find使用排序规则

Collation collation = Collation.of("de");

Query query = new Query(Criteria.where("firstName").is("Amél")).collation(collation);

List<Person> results = template.find(query, Person.class);

例子 75.对aggregate使用排序规则

Collation collation = Collation.of("de");

AggregationOptions options = AggregationOptions.builder().collation(collation).build();

Aggregation aggregation = newAggregation(
  project("tags"),
  unwind("tags"),
  group("tags")
    .count().as("count")
).withOptions(options);

AggregationResults<TagCount> results = template.aggregate(aggregation, "tags", TagCount.class);

Warning

仅当用于操作的排序规则与索引排序规则匹配时,才使用索引。

JSON Schema

从 3.6 版开始,MongoDB 支持根据提供的JSON Schema验证文档的集合。可以在创建集合时定义架构本身以及验证操作和级别,如以下示例所示:

例子 76.samplesJSON 模式

{
  "type": "object",                                                        (1)

  "required": [ "firstname", "lastname" ],                                 (2)

  "properties": {                                                          (3)

    "firstname": {                                                         (4)
      "type": "string",
      "enum": [ "luke", "han" ]
    },
    "address": {                                                           (5)
      "type": "object",
      "properties": {
        "postCode": { "type": "string", "minLength": 4, "maxLength": 5 }
      }
    }
  }
}
  • (1) JSON 模式文档始终从其根目录描述整个文档。模式是一个模式对象本身,可以包含描述属性和子文档的嵌入式模式对象。
  • (2) required是一个属性,用于描述文档中需要哪些属性。可以选择指定它,以及其他模式约束。请参阅available keywords上的 MongoDB 文档。
  • (3) properties与描述object类型的架构对象相关。它包含特定于属性的架构约束。
  • (4) firstname指定文档中firsname字段的约束。在这里,它是一个基于字符串的properties元素,声明可能的字段值。
  • (5) address是一个子文档,定义了其postCode字段中的值的架构。

您可以通过指定模式文档(即使用Document API 解析或构建文档对象)或通过使用org.springframework.data.mongodb.core.schema中的 Spring Data 的 JSON 模式 Util 来构建模式来提供模式。 MongoJsonSchema是所有与 JSON 模式相关的操作的入口点。以下示例显示了如何使用MongoJsonSchema.builder()创建 JSON 模式:

例子 77.创建一个 JSON 模式

MongoJsonSchema.builder()                                                    (1)
    .required("lastname")                                                    (2)

    .properties(
                required(string("firstname").possibleValues("luke", "han")), (3)

                object("address")
                     .properties(string("postCode").minLength(4).maxLength(5)))

    .build();                                                                (4)
  • (1) 获取模式构建器以使用流畅的 API 配置模式。
  • (2) 如此处所示直接配置必需的属性,或者如第 3 步中所述更详细地配置。
  • (3) 配置必需的字符串类型的firstname字段,仅允许lukehan值。属性可以 Importing 或不 Importing。使用JsonSchemaProperty的静态导入可以使语法稍微紧凑一些,并获得诸如string(…)之类的入口点。
  • (4) 构建模式对象。使用该架构创建集合或query documents

通过网关接口上的静态方法,已经有一些 sched 义和强类型化架构对象(JsonSchemaObjectJsonSchemaProperty)。但是,您可能需要构建可通过构建器 API 创建的定制属性验证规则,如以下示例所示:

// "birthdate" : { "bsonType": "date" }
JsonSchemaProperty.named("birthdate").ofType(Type.dateType());

// "birthdate" : { "bsonType": "date", "description", "Must be a date" }
JsonSchemaProperty.named("birthdate").with(JsonSchemaObject.of(Type.dateType()).description("Must be a date"));

CollectionOptions提供了对集合的架构支持的入口点,如以下示例所示:

例子 78.用$jsonSchema创建集合

MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
生成架构

设置模式可能是一项耗时的任务,我们鼓励每个决定这样做的人都花点时间。重要的是,架构更改可能很困难。但是,有时您可能不想对此一视同仁,而这正是JsonSchemaCreator发挥作用的地方。

JsonSchemaCreator及其默认实现从 Map 基础结构提供的域类型元数据中生成MongoJsonSchema。这意味着,要考虑annotated properties和潜在的custom conversions

例子 79.从域类型生成 Json Schema

public class Person {

    private final String firstname;                   (1)
    private final int age;                            (2)
    private Species species;                          (3)
    private Address address;                          (4)
    private @Field(fieldType=SCRIPT) String theForce; (5)
    private @Transient Boolean useTheForce;           (6)

    public Person(String firstname, int age) {        (1) (2)

        this.firstname = firstname;
        this.age = age;
    }

    // gettter / setter omitted
}

MongoJsonSchema schema = MongoJsonSchemaCreator.create(mongoOperations.getConverter())
    .createSchemaFor(Person.class);

template.createCollection(Person.class, CollectionOptions.empty().schema(schema));
{
    'type' : 'object',
    'required' : ['age'],                     (2)
    'properties' : {
        'firstname' : { 'type' : 'string' },  (1)
        'age' : { 'bsonType' : 'int' }        (2)
        'species' : {                         (3)
            'type' : 'string',
            'enum' : ['HUMAN', 'WOOKIE', 'UNKNOWN']
        }
        'address' : {                         (4)
            'type' : 'object'
            'properties' : {
                'postCode' : { 'type': 'string' }
            }
        },
        'theForce' : { 'type' : 'javascript'} (5)
     }
}
  • (1) 简单对象属性被视为常规属性。
  • (2) 原始类型被视为必需的属性
  • (3) 枚举仅限于可能的值。
  • (4) 检查对象类型属性并将其表示为嵌套文档。
  • (5) String类型属性,由转换器转换为Code
  • (6) @Transient属性在生成模式时被省略。

Note

除非可以通过@MongoIdComments 获得更多特定信息,否则使用可以转换为ObjectId的类型(例如String)的_id属性将 Map 到{ type : 'object' }

表 2.分隔模式生成规则

Java Schema Type Notes
Object type : object 如果可用的元数据,则使用properties
Collection type : array -
Map type : object -
Enum type : string enum属性包含可能的枚举值。
array type : array 简单类型数组,除非它是byte[]
byte[] bsonType : binData -
查询集合以匹配 JSON 模式

您可以使用模式查询任何集合中与 JSON 模式定义的给定结构相匹配的文档,如以下示例所示:

例子 80.查询与$jsonSchema匹配的文档

MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

template.find(query(matchingDocumentStructure(schema)), Person.class);
Encrypted Fields

MongoDB 4.2 场级加密允许直接加密单个属性。

如下面的示例所示,在设置 JSON 模式时,可以将属性包装在加密的属性中。

例子 81.通过 Json Schema 进行 Client 端字段级加密

MongoJsonSchema schema = MongoJsonSchema.builder()
    .properties(
        encrypted(string("ssn"))
            .algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
            .keyId("*key0_id")
	).build();

Note

确保将驱动程序com.mongodb.AutoEncryptionSettings设置为使用 Client 端加密。 MongoDB 不支持所有字段类型的加密。特定数据类型需要确定性加密才能保留相等性比较功能。

JSON 架构类型

下表显示了受支持的 JSON 模式类型:

表 3.支持的 JSON 模式类型

Schema Type Java Type Schema Properties
untyped - description,生成的descriptionenumallOfanyOfoneOfnot
object Object required , additionalProperties , properties , minProperties , maxProperties , patternProperties
array byte[]以外的任何数组 uniqueItems , additionalItems , items , minItems , maxItems
string String minLength , maxLentgth , pattern
int int , Integer multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
long long , Long multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
double float , Float , double , Double multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
decimal BigDecimal multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
number Number multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
binData byte[] (none)
boolean boolean , Boolean (none)
null null (none)
objectId ObjectId (none)
date java.util.Date (none)
timestamp BsonTimestamp (none)
regex java.util.regex.Pattern (none)

Note

untyped是由所有类型化架构类型继承的通用类型。它为键入的架构类型提供所有untyped架构属性。

有关更多信息,请参见$jsonSchema

MongoDB Repositories通过@Query注解的collation属性支持Collations

例子 82.对存储库的归类支持

public interface PersonRepository extends MongoRepository<Person, String> {

  @Query(collation = "en_US")  (1)
  List<Person> findByFirstname(String firstname);

  @Query(collation = "{ 'locale' : 'en_US' }") (2)
  List<Person> findPersonByFirstname(String firstname);

  @Query(collation = "?1") (3)
  List<Person> findByFirstname(String firstname, Object collation);

  @Query(collation = "{ 'locale' : '?1' }") (4)
  List<Person> findByFirstname(String firstname, String collation);

  List<Person> findByFirstname(String firstname, Collation collation); (5)

  @Query(collation = "{ 'locale' : 'en_US' }")
  List<Person> findByFirstname(String firstname, @Nullable Collation collation); (6)
}
  • (1) 静态归类定义产生{ 'locale' : 'en_US' }
  • (2) 静态归类定义产生{ 'locale' : 'en_US' }
  • (3) 动态排序规则取决于第二个方法参数。允许的类型包括String(例如'en_US'),Locacle(例如 Locacle.US)和Document(例如 new Document(“ locale”,“ en_US”))
  • (4) 动态排序规则取决于第二个方法参数。
  • (5)Collation方法参数应用于查询。
  • (6) 如果不为空,则Collation方法参数会覆盖@Query中的默认collation

Note

如果为存储库查找器方法启用了自动索引创建功能,则在创建索引时将包含潜在的静态归类定义,如(1)和(2)所示。

Tip

最具体的Collation可能定义了其他规则。这意味着方法参数超过了查询方法 Comments,超过了 doamin 类型 Comments。

10.6.8. JSON 模式

从 3.6 版开始,MongoDB 支持根据提供的JSON Schema验证文档的集合。可以在创建集合时定义架构本身以及验证操作和级别,如以下示例所示:

例子 83.samplesJSON 模式

{
  "type": "object",                                                        (1)

  "required": [ "firstname", "lastname" ],                                 (2)

  "properties": {                                                          (3)

    "firstname": {                                                         (4)
      "type": "string",
      "enum": [ "luke", "han" ]
    },
    "address": {                                                           (5)
      "type": "object",
      "properties": {
        "postCode": { "type": "string", "minLength": 4, "maxLength": 5 }
      }
    }
  }
}
  • (1) JSON 模式文档始终从其根目录描述整个文档。模式是一个模式对象本身,可以包含描述属性和子文档的嵌入式模式对象。
  • (2) required是一个属性,用于描述文档中需要哪些属性。可以选择指定它,以及其他模式约束。请参阅available keywords上的 MongoDB 文档。
  • (3) properties与描述object类型的架构对象相关。它包含特定于属性的架构约束。
  • (4) firstname指定文档中firsname字段的约束。在这里,它是一个基于字符串的properties元素,声明可能的字段值。
  • (5) address是一个子文档,定义了其postCode字段中的值的架构。

您可以通过指定模式文档(即使用Document API 解析或构建文档对象)或通过使用org.springframework.data.mongodb.core.schema中的 Spring Data 的 JSON 模式 Util 来构建模式来提供模式。 MongoJsonSchema是所有与 JSON 模式相关的操作的入口点。以下示例显示了如何使用MongoJsonSchema.builder()创建 JSON 模式:

例子 84.创建一个 JSON 模式

MongoJsonSchema.builder()                                                  (1)
    .required("firstname", "lastname")                                     (2)

    .properties(
                string("firstname").possibleValues("luke", "han"),         (3)

                object("address")
                     .properties(string("postCode").minLength(4).maxLength(5)))

    .build();                                                              (4)
  • (1) 获取模式构建器以使用流畅的 API 配置模式。
  • (2) 配置必需的属性。
  • (3) 配置字符串类型的firstname字段,仅允许lukehan值。属性可以 Importing 或不 Importing。使用JsonSchemaProperty的静态导入可以使语法稍微紧凑一些,并获得诸如string(…)之类的入口点。
  • (4) 构建模式对象。使用该架构创建集合或query documents

通过网关接口上的静态方法,已经有一些 sched 义和强类型化架构对象(JsonSchemaObjectJsonSchemaProperty)。但是,您可能需要构建可通过构建器 API 创建的定制属性验证规则,如以下示例所示:

// "birthdate" : { "bsonType": "date" }
JsonSchemaProperty.named("birthdate").ofType(Type.dateType());

// "birthdate" : { "bsonType": "date", "description", "Must be a date" }
JsonSchemaProperty.named("birthdate").with(JsonSchemaObject.of(Type.dateType()).description("Must be a date"));

CollectionOptions提供了对集合的架构支持的入口点,如以下示例所示:

例子 85.用$jsonSchema创建集合

MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

template.createCollection(Person.class, CollectionOptions.empty().schema(schema));

您可以使用模式查询任何集合中与 JSON 模式定义的给定结构相匹配的文档,如以下示例所示:

例子 86.查询与$jsonSchema匹配的文档

MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname").build();

template.find(query(matchingDocumentStructure(schema)), Person.class);

下表显示了受支持的 JSON 模式类型:

表 4.支持的 JSON 模式类型

Schema Type Java Type Schema Properties
untyped - description,生成的descriptionenumallOfanyOfoneOfnot
object Object required , additionalProperties , properties , minProperties , maxProperties , patternProperties
array byte[]以外的任何数组 uniqueItems , additionalItems , items , minItems , maxItems
string String minLength , maxLentgth , pattern
int int , Integer multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
long long , Long multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
double float , Float , double , Double multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
decimal BigDecimal multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
number Number multipleOf , minimum , exclusiveMinimum , maximum , exclusiveMaximum
binData byte[] (none)
boolean boolean , Boolean (none)
null null (none)
objectId ObjectId (none)
date java.util.Date (none)
timestamp BsonTimestamp (none)
regex java.util.regex.Pattern (none)

Note

untyped是由所有类型化架构类型继承的通用类型。它为键入的架构类型提供所有untyped架构属性。

有关更多信息,请参见$jsonSchema

10.6.9. Fluent 的模板 API

MongoOperations接口是与 MongoDB 进行更底层交互时的核心组件之一。它提供了广泛的方法,涵盖了从集合创建,索引创建和 CRUD 操作到更高级的功能(如 Map-Reduce 和聚合)的需求。您可以为每个方法找到多个重载。其中大多数涵盖了 API 的可选部分或可为空的部分。

FluentMongoOperationsMongoOperations的常用方法提供了更窄的界面,并提供了更具可读性的 FluentAPI。入口点(insert(…)find(…)update(…)等)基于要运行的操作遵循自然的命名模式。从入口点开始,API 被设计为仅提供上下文相关的方法,这些方法导致终止方法调用实际的MongoOperations对应对象,在以下示例中为all方法:

List<SWCharacter> all = ops.find(SWCharacter.class)
  .inCollection("star-wars")                        (1)
  .all();
  • (1) 如果SWCharacter使用@Document定义了集合,或者如果您使用类名作为集合名称,则跳过此步骤。

有时,MongoDB 中的集合包含不同类型的实体,例如SWCharacters集合中的Jedi。要对Query和返回值 Map 使用不同的类型,可以使用as(Class<?> targetType)来不同地 Map 结果,如以下示例所示:

List<Jedi> all = ops.find(SWCharacter.class)    (1)
  .as(Jedi.class)                               (2)
  .matching(query(where("jedi").is(true)))
  .all();
  • (1) 查询字段是针对SWCharacter类型 Map 的。
  • (2) 结果文档被 Map 到Jedi

Tip

您可以通过as(Class<?>)提供目标类型,直接将Projections应用于结果文档。

Note

使用投影允许MongoTemplate通过将实际响应限制为投影目标类型所需的字段来优化结果 Map。只要Query本身不包含任何字段限制,并且目标类型是封闭接口或 DTO 投影,则这适用。

您可以通过终止方法first()one()all()stream()来检索单个实体和将多个实体检索为ListStream

当使用near(NearQuery)编写地理空间查询时,终止方法的数量被更改为仅包括对于在 MongoDB 中执行geoNear命令有效的方法(在GeoResults中以GeoResult的形式获取实体),如以下示例所示:

GeoResults<Jedi> results = mongoOps.query(SWCharacter.class)
  .as(Jedi.class)
  .near(alderaan) // NearQuery.near(-73.9667, 40.78).maxDis…
  .all();

10.6.10. Kotlin 的类型安全查询

Kotlin 通过其语言语法和扩展系统来支持特定领域的语言创建。 Spring Data MongoDB 附带了Criteria的 Kotlin 扩展,使用Kotlin 属性参考来构建类型安全的查询。使用此扩展的查询通常可从提高的可读性中受益。 Criteria上的大多数关键字具有匹配的 Kotlinextensions,例如inValuesregex

考虑下面的示例,解释类型安全查询:

import org.springframework.data.mongodb.core.query.*

mongoOperations.find<Book>(
  Query(Book::title isEqualTo "Moby-Dick")               (1)
)

mongoOperations.find<Book>(
  Query(titlePredicate = Book::title exists true)
)

mongoOperations.find<Book>(
  Criteria().andOperator(
    Book::price gt 5,
    Book::price lt 10
  )
)

// Binary operators
mongoOperations.find<BinaryMessage>(
  Query(BinaryMessage::payload bits { allClear(0b101) }) (2)
)

// Nested Properties (i.e. refer to "book.author")
mongoOperations.find<Book>(
  Query(Book::author / Author::name regex "^H")          (3)
)
  • (1) isEqualTo()是具有扩展类型KProperty<T>的后缀扩展函数,该函数返回Criteria
  • (2) 对于按位运算符,请在您调用Criteria.BitwiseCriteriaOperators的方法之一的地方传递一个 lambda 参数。
  • (3) 要构造嵌套属性,请使用/字符(重载的运算符div)。

10.6.11. 其他查询选项

MongoDB 提供了多种将元信息(例如注解或批处理大小)应用于查询的方法。直接使用Query API,有几种方法可用于这些选项。

Query query = query(where("firstname").is("luke"))
    .comment("find luke")         (1)
    .batchSize(100)                                 (2)
    .slaveOk();                                     (3)
  • (1) Comments 传播到 MongoDB 配置文件日志。
  • (2) 每个响应批次中要返回的文档数。
  • (3) 允许查询副本从属。

在存储库级别,@MetaComments 提供了以声明方式添加查询选项的方法。

@Meta(comment = "find luke", batchSize = 100, flags = { SLAVE_OK })
List<Person> findByFirstname(String firstname);

10.7. 实例查询

10.7.1. Introduction

本章对“示例查询”进行了介绍,并说明了如何使用它。

示例查询(QBE)是一种具有简单界面的用户友好查询技术。它允许动态查询创建,并且不需要您编写包含字段名称的查询。实际上,“示例查询”根本不需要您使用 Store 特定的查询语言编写查询。

10.7.2. Usage

按示例查询 API 包含三部分:

  • 探针:带有填充字段的域对象的实际示例。

  • ExampleMatcherExampleMatcher包含有关如何匹配特定字段的详细信息。可以在多个示例中重复使用它。

  • ExampleExample由探针和ExampleMatcher组成。它用于创建查询。

按示例查询非常适合几种用例:

  • 使用一组静态或动态约束来查询数据存储。

  • 频繁重构域对象,而不必担心破坏现有查询。

  • 独立于基础数据存储区 API 进行工作。

按示例查询也有一些限制:

  • 不支持嵌套或分组属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2)

  • 仅支持字符串的开始/包含/结束/正则表达式匹配,以及其他属性类型的完全匹配。

在开始使用“示例查询”之前,您需要具有一个域对象。首先,为您的存储库创建一个接口,如以下示例所示:

例子 87. Sample Person 对象

public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

前面的示例显示了一个简单的域对象。您可以使用它来创建Example。默认情况下,具有null值的字段将被忽略,并且使用 Store 特定的默认值来匹配字符串。可以使用of factory 方法或ExampleMatcher来构建示例。 Example是不可变的。以下清单显示了一个简单的示例:

例子 88.简单的例子

Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

Example<Person> example = Example.of(person);         (3)
  • (1) 创建域对象的新实例。
  • (2) 设置要查询的属性。
  • (3) 创建Example

最好在存储库中执行示例。为此,让您的存储库界面扩展QueryByExampleExecutor<T>。以下清单显示了QueryByExampleExecutor界面的摘录:

例子 89. QueryByExampleExecutor

public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  // … more functionality omitted.
}

10.7.3. 示例匹配器

示例不限于默认设置。您可以使用ExampleMatcher为字符串匹配,空值处理和特定于属性的设置指定自己的默认值,如以下示例所示:

例子 90.具有定制匹配的例子匹配器

Person person = new Person();                          (1)
person.setFirstname("Dave");                           (2)

ExampleMatcher matcher = ExampleMatcher.matching()     (3)
  .withIgnorePaths("lastname")                         (4)
  .withIncludeNullValues()                             (5)
  .withStringMatcherEnding();                          (6)

Example<Person> example = Example.of(person, matcher); (7)
  • (1) 创建域对象的新实例。
  • (2) 设置属性。
  • (3) 创建一个ExampleMatcher以期望所有值都匹配。即使没有进一步配置,在此阶段也可以使用。
  • (4) 构造一个新的ExampleMatcher以忽略lastname属性路径。
  • (5) 构造一个新的ExampleMatcher以忽略lastname属性路径并包含空值。
  • (6) 构造一个新的ExampleMatcher来忽略lastname属性路径,包括空值并执行后缀字符串匹配。
  • (7) 基于域对象和配置的ExampleMatcher创建一个新的Example

默认情况下,ExampleMatcher期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何谓词匹配的结果,请使用ExampleMatcher.matchingAny()

您可以为单个属性(例如“名字”和“姓氏”,或者对于嵌套属性,“ address.city”)指定行为。您可以使用匹配选项和区分大小写对其进行调整,如以下示例所示:

例子 91.配置匹配器选项

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

配置匹配器选项的另一种方法是使用 lambda(在 Java 8 中引入)。此方法创建一个回调,要求实现者修改匹配器。您不需要返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用 lambda 的匹配器:

例子 92.用 lambdas 配置匹配器选项

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

Example创建的查询使用配置的合并视图。可以在ExampleMatcher级别上设置默认的匹配设置,而可以将个别设置应用于特定的属性路径。除非明确定义,否则ExampleMatcher上设置的设置将由属性路径设置继承。属性修补程序上的设置优先于默认设置。下表描述了各种ExampleMatcher设置的范围:

表 5. ExampleMatcher设置的范围

Setting Scope
Null-handling ExampleMatcher
String matching ExampleMatcher和属性路径
Ignoring properties Property path
Case sensitivity ExampleMatcher和属性路径
Value transformation Property path

10.7.4. 运行一个例子

以下示例显示了使用存储库(在这种情况下为Person个对象)时如何通过示例进行查询:

例子 93.使用存储库按例子查询

public interface PersonRepository extends QueryByExampleExecutor<Person> {

}

public class PersonService {

  @Autowired PersonRepository personRepository;

  public List<Person> findPeople(Person probe) {
    return personRepository.findAll(Example.of(probe));
  }
}

包含未类型化ExampleSpecExample使用存储库类型及其集合名称。类型化的ExampleSpec实例使用其类型作为结果类型,并使用Repository实例中的集合名称。

Note

当在ExampleSpec中包括null值时,Spring Data Mongo 使用嵌入式文档匹配而不是点表示法属性匹配。这样做会强制对嵌入文档中的所有属性值和属性 Sequences 进行精确的文档匹配。

Spring Data MongoDB 为以下匹配选项提供支持:

表 6. StringMatcher选项

Matching Logical result
DEFAULT(区分大小写) {"firstname" : firstname}
DEFAULT(不区分大小写) {"firstname" : { $regex: firstname, $options: 'i'}}
EXACT(区分大小写) {"firstname" : { $regex: /^firstname$/}}
EXACT(不区分大小写) {"firstname" : { $regex: /^firstname$/, $options: 'i'}}
STARTING(区分大小写) {"firstname" : { $regex: /^firstname/}}
STARTING(不区分大小写) {"firstname" : { $regex: /^firstname/, $options: 'i'}}
ENDING(区分大小写) {"firstname" : { $regex: /firstname$/}}
ENDING(不区分大小写) {"firstname" : { $regex: /firstname$/, $options: 'i'}}
CONTAINING(区分大小写) {"firstname" : { $regex: /.*firstname.*/}}
CONTAINING(不区分大小写) {"firstname" : { $regex: /.*firstname.*/, $options: 'i'}}
REGEX(区分大小写) {"firstname" : { $regex: /firstname/}}
REGEX(不区分大小写) {"firstname" : { $regex: /firstname/, $options: 'i'}}

10.7.5. 未 Importing 示例

默认情况下,严格键入Example。这意味着 Map 的查询具有包含的类型匹配,将其限制为探测可分配的类型。例如,当使用默认类型键(_class)时,查询具有诸如(_class : { $in : [ com.acme.Person] })之类的限制。

通过使用UntypedExampleMatcher,可以绕过默认行为并跳过类型限制。因此,只要字段名称匹配,几乎任何域类型都可以用作创建引用的探针,如以下示例所示:

例子 94.无类型例子查询

class JustAnArbitraryClassWithMatchingFieldName {
  @Field("lastname") String value;
}

JustAnArbitraryClassWithMatchingFieldNames probe = new JustAnArbitraryClassWithMatchingFieldNames();
probe.value = "stark";

Example example = Example.of(probe, UntypedExampleMatcher.matching());

Query query = new Query(new Criteria().alike(example));
List<Person> result = template.find(query, Person.class);

10.8. Map 减少操作

您可以使用 Map-Reduce 查询 MongoDB,这对于批处理,数据聚合以及查询语言不能满足您的需求很有用。

通过在MongoOperations上提供方法来简化 Map-Reduce 操作的创建和执行,Spring 提供了与 MongoDB 的 Map-Reduce 的集成。它可以将 Map-Reduce 操作的结果转换为 POJO 并与 Spring 的Resource abstraction集成。这使您可以将 JavaScript 文件放置在文件系统,Classpath,HTTP 服务器或任何其他 Spring Resource 实现上,然后通过简单的 URI 样式语法(例如classpath:reduce.js;)来引用 JavaScript 资源。将文件中的 JavaScript 代码外部化通常比将它们作为 Java 字符串嵌入代码中更可取。请注意,您仍然可以根据需要将 JavaScript 代码作为 Java 字符串传递。

10.8.1. 用法示例

为了了解如何执行 Map-Reduce 操作,我们使用* MongoDB-The Definitive Guide *[1]一书中的示例。在此示例中,我们创建三个文档,其值分别为[a,b],[b,c]和[c,d]。如下例所示,每个文档中的值都与键“ x”相关联(假设这些文档位于名为jmr1的集合中):

{ "_id" : ObjectId("4e5ff893c0277826074ec533"), "x" : [ "a", "b" ] }
{ "_id" : ObjectId("4e5ff893c0277826074ec534"), "x" : [ "b", "c" ] }
{ "_id" : ObjectId("4e5ff893c0277826074ec535"), "x" : [ "c", "d" ] }

以下 Map 函数计算每个文档数组中每个字母的出现:

function () {
    for (var i = 0; i < this.x.length; i++) {
        emit(this.x[i], 1);
    }
}

以下缩减功能汇总了所有文档中每个字母的出现情况:

function (key, values) {
    var sum = 0;
    for (var i = 0; i < values.length; i++)
        sum += values[i];
    return sum;
}

运行上述功能将得到以下集合:

{ "_id" : "a", "value" : 1 }
{ "_id" : "b", "value" : 2 }
{ "_id" : "c", "value" : 2 }
{ "_id" : "d", "value" : 1 }

假设 map 和 reduce 函数位于map.jsreduce.js中并且 Binding 在 jar 中,因此它们在 Classpath 中可用,则可以按以下方式运行 Map-Reduce 操作:

MapReduceResults<ValueObject> results = mongoOperations.mapReduce("jmr1", "classpath:map.js", "classpath:reduce.js", ValueObject.class);
for (ValueObject valueObject : results) {
  System.out.println(valueObject);
}

前面的示例产生以下输出:

ValueObject [id=a, value=1.0]
ValueObject [id=b, value=2.0]
ValueObject [id=c, value=2.0]
ValueObject [id=d, value=1.0]

MapReduceResults类实现Iterable,并提供对原始输出以及计时和计数统计信息的访问。以下清单显示了ValueObject类:

public class ValueObject {

  private String id;
  private float value;

  public String getId() {
    return id;
  }

  public float getValue() {
    return value;
  }

  public void setValue(float value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return "ValueObject [id=" + id + ", value=" + value + "]";
  }
}

默认情况下,使用INLINE的输出类型,因此您无需指定输出集合。要指定其他 Map-Reduce 选项,请使用带有额外MapReduceOptions参数的重载方法。 MapReduceOptions类具有流畅的 API,因此可以以紧凑的语法添加其他选项。以下示例将输出集合设置为jmr1_out(请注意,仅将输出集合设置为默认输出类型REPLACE):

MapReduceResults<ValueObject> results = mongoOperations.mapReduce("jmr1", "classpath:map.js", "classpath:reduce.js",
                                                                     new MapReduceOptions().outputCollection("jmr1_out"), ValueObject.class);

还有一个静态导入(import static org.springframework.data.mongodb.core.mapreduce.MapReduceOptions.options;)可用于使语法稍微紧凑一些,如以下示例所示:

MapReduceResults<ValueObject> results = mongoOperations.mapReduce("jmr1", "classpath:map.js", "classpath:reduce.js",
                                                                     options().outputCollection("jmr1_out"), ValueObject.class);

您还可以指定查询以减少 Importing 到 Map-Reduce 操作中的数据集。下面的示例从 Map-Reduce 操作的考虑中删除了包含[a,b]的文档:

Query query = new Query(where("x").ne(new String[] { "a", "b" }));
MapReduceResults<ValueObject> results = mongoOperations.mapReduce(query, "jmr1", "classpath:map.js", "classpath:reduce.js",
                                                                     options().outputCollection("jmr1_out"), ValueObject.class);

请注意,您可以在查询中指定其他限制和排序值,但不能跳过值。

10.9. 脚本操作

Warning

MongoDB 4.2删除了对ScriptOperations使用的eval命令的支持。
无法替代已删除的功能。

MongoDB 允许通过直接发送脚本或调用存储的脚本来在服务器上执行 JavaScript 函数。 ScriptOperations可以通过MongoTemplate进行访问,并提供JavaScript用法的基本抽象。以下示例说明了如何使用ScriptOperations类:

ScriptOperations scriptOps = template.scriptOps();

ExecutableMongoScript echoScript = new ExecutableMongoScript("function(x) { return x; }");
scriptOps.execute(echoScript, "directly execute script");     (1)

scriptOps.register(new NamedMongoScript("echo", echoScript)); (2)
scriptOps.call("echo", "execute script via name");            (3)
  • (1) 无需在服务器端存储函数即可直接执行脚本。
  • (2) 使用'echo'作为其名称存储脚本。给定名称标识脚本,并允许以后调用它。
  • (3) 使用提供的参数以名称“ echo”执行脚本。

10.10. 集团运作

作为使用 Map-Reduce 进行数据聚合的一种替代方法,您可以使用group operation,它感觉与使用 SQL 的 group by 查询样式相似,因此与使用 Map-Reduce 相比,它可能更平易近人。使用组操作确实有一些限制,例如,在共享环境中不支持该操作,并且它在单个 BSON 对象中返回完整的结果集,因此结果应该很小,少于 10,000 个键。

通过在 MongoOperations 上提供方法来简化组操作的创建和执行,Spring 提供了与 MongoDB 组操作的集成。它可以将分组操作的结果转换为 POJO,还可以与 Spring 的Resource abstraction抽象集成。这将使您可以将 JavaScript 文件放置在文件系统,Classpath,http 服务器或任何其他 Spring Resource 实现上,然后通过简单的 URI 样式语法(例如)引用 JavaScript 资源。 'classpath:reduce.js;。如果通常比将它们作为 Java 字符串嵌入代码中更可取,则可以将文件中的 JavaScript 代码外部化。请注意,您仍然可以根据需要将 JavaScript 代码作为 Java 字符串传递。

10.10.1. 用法示例

为了理解组操作如何工作,使用了以下示例,该示例有些虚构。有关更实际的示例,请参阅《 MongoDB-Authority 指南》一书。使用以下行创建了名为group_test_collection的集合。

{ "_id" : ObjectId("4ec1d25d41421e2015da64f1"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f2"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f3"), "x" : 2 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f4"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f5"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f6"), "x" : 3 }

我们希望将每一行中唯一的字段x分组,并汇总每个x的特定值出现的次数。为此,我们需要创建一个初始文档,其中包含我们的 count 变量以及一个 reduce 函数,该函数将在每次遇到它时将其递增。执行组操作的 Java 代码如下所示

GroupByResults<XObject> results = mongoTemplate.group("group_test_collection",
                                                      GroupBy.key("x").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1 }"),
                                                      XObject.class);

第一个参数是运行分组操作的集合的名称,第二个参数是 Fluent 的 API,该 API 通过GroupBy类指定分组操作的属性。在此示例中,我们仅使用intialDocumentreduceFunction方法。您还可以指定键功能以及终结器作为 fluent API 的一部分。如果您有多个要分组的密钥,则可以传入以逗号分隔的密钥列表。

组操作的原始结果是一个看起来像这样的 JSON 文档

{
  "retval" : [ { "x" : 1.0 , "count" : 2.0} ,
               { "x" : 2.0 , "count" : 1.0} ,
               { "x" : 3.0 , "count" : 3.0} ] ,
  "count" : 6.0 ,
  "keys" : 3 ,
  "ok" : 1.0
}

“ retval”字段下的文档被 Map 到组方法中的第三个参数,在本例中为 XObject,如下所示。

public class XObject {

  private float x;

  private float count;

  public float getX() {
    return x;
  }

  public void setX(float x) {
    this.x = x;
  }

  public float getCount() {
    return count;
  }

  public void setCount(float count) {
    this.count = count;
  }

  @Override
  public String toString() {
    return "XObject [x=" + x + " count = " + count + "]";
  }
}

您还可以通过调用GroupByResults类上的方法getRawResults来获得原始结果Document

MongoOperations上的 group 方法还有其他方法重载,可让您指定Criteria对象以选择行的子集。下面显示了一个示例,该示例使用Criteria对象以及使用静态导入的一些语法糖,以及通过 Spring Resource 字符串引用键函数和 reduce 函数的 javascript 文件。

import static org.springframework.data.mongodb.core.mapreduce.GroupBy.keyFunction;
import static org.springframework.data.mongodb.core.query.Criteria.where;

GroupByResults<XObject> results = mongoTemplate.group(where("x").gt(0),
                                        "group_test_collection",
                                        keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }").reduceFunction("classpath:groupReduce.js"), XObject.class);

10.11. 聚合框架支持

Spring Data MongoDB 提供对 2.2 版中引入的 MongoDB 的聚合框架的支持。

有关更多信息,请参阅完整的reference documentation聚合框架和用于 MongoDB 的其他数据聚合工具。

10.11.1. 基本概念

Spring Data MongoDB 中对 Aggregation Framework 的支持基于以下关键抽象:AggregationAggregationOperationAggregationResults

  • Aggregation

Aggregation代表 MongoDB aggregate操作,并保留对聚合管道指令的描述。通过调用Aggregation类的适当的newAggregation(…)静态工厂方法来创建聚合,该方法采用AggregateOperation的列表和一个可选的 Importing 类。

实际的聚合操作由MongoTemplateaggregate方法执行,该方法将所需的输出类作为参数。

  • TypedAggregation

Aggregation一样,TypedAggregation包含聚合管道的指令和对 Importing 类型的引用,该引用用于将域属性 Map 到实际文档字段。

在执行时,将考虑给定的 Importing 类型,并考虑潜在的@FieldComments 并在引用不存在的属性时引发错误,从而对字段引用进行检查。

  • AggregationOperation

AggregationOperation代表 MongoDB 聚合管道操作,并描述了应在此聚合步骤中执行的处理。尽管您可以手动创建AggregationOperation,但是我们建议使用Aggregate类提供的静态工厂方法来构造AggregateOperation

  • AggregationResults

AggregationResults是汇总操作结果的容器。它以Document的形式提供对原始聚合结果的访问,以Document形式 Map 到对象以及有关聚合的其他信息。

以下清单显示了使用 Spring Data MongoDB 对 MongoDB 聚合框架的支持的规范示例:

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

Aggregation agg = newAggregation(
    pipelineOP1(),
    pipelineOP2(),
    pipelineOPn()
);

AggregationResults<OutputType> results = mongoTemplate.aggregate(agg, "INPUT_COLLECTION_NAME", OutputType.class);
List<OutputType> mappedResult = results.getMappedResults();

请注意,如果您提供 Importing 类作为newAggregation方法的第一个参数,则MongoTemplate将从该类派生 Importing 集合的名称。否则,如果不指定 Importing 类,则必须显式提供 Importing 集合的名称。如果同时提供了 Importing 类和 Importing 集合,则后者优先。

10.11.2. 支持的聚合操作

MongoDB 聚合框架提供以下类型的聚合操作:

  • 管道聚合运算符

  • 组汇总运算符

  • 布尔聚合运算符

  • 比较聚合运算符

  • 算术聚合运算符

  • 字符串聚合运算符

  • 日期汇总运算符

  • 数组聚合运算符

  • 条件聚合运算符

  • 查找聚合运算符

  • 转换聚合运算符

  • 对象聚合运算符

在撰写本文时,我们为 Spring Data MongoDB 中的以下聚合操作提供支持:

表 7. Spring Data MongoDB 当前支持的聚合操作

管道聚合运算符 bucket , bucketAuto , count , facet , geoNear , graphLookup , group , limit , lookup , match , project , replaceRoot , skip , sort , unwind
集合聚合运算符 setEquals , setIntersection , setUnion , setDifference , setIsSubset , anyElementTrue , allElementsTrue
组汇总运算符 addToSet , first , last , max , min , avg , push , sum , (*count) , stdDevPop , stdDevSamp
算术聚合运算符 absadd(*通过plus),ceildivideexpfloorlnloglog10modmultiplypowsqrtsubtract(*通过minus),trunc
字符串聚合运算符 concat , substr , toLower , toUpper , stcasecmp , indexOfBytes , indexOfCP , split , strLenBytes , strLenCP , substrCP , trim , ltrim , rtim
比较聚合运算符 eq(* via:is),gtgteltltene
数组聚合运算符 arrayElementAt , arrayToObject , concatArrays , filter , in , indexOfArray , isArray , range , reverseArray , reduce , size , slice , zip
Literal Operators literal
日期汇总运算符 dayOfYear , dayOfMonth , dayOfWeek , year , month , week , hour , minute , second , millisecond , dateToString , dateFromString , dateFromParts , dateToParts , isoDayOfWeek , isoWeek , isoWeekYear
Variable Operators map
条件聚合运算符 cond , ifNull , switch
类型聚合运算符 type
转换聚合运算符 convert , toBool , toDate , toDecimal , toDouble , toInt , toLong , toObjectId , toString
对象聚合运算符 objectToArray , mergeObjects
  • 该操作由 Spring Data MongoDBMap 或添加。

请注意,Spring Data MongoDB 当前不支持此处未列出的聚合操作。比较聚合运算符表示为Criteria表达式。

10.11.3. 投影表达式

投影表达式用于定义作为特定聚合步骤结果的字段。可以通过Aggregation类的project方法定义投影表达式,方法是传递String对象的列表或聚合框架Fields对象。可以使用and(String)方法通过 fluent API 使用其他字段扩展投影,并可以通过as(String)方法使用别名。请注意,您还可以使用聚合框架的Fields.field静态工厂方法来定义具有别名的字段,然后使用该方法来构造新的Fields实例。在后面的聚合阶段中对投影字段的引用仅对包含字段或其别名(包括新定义的字段及其别名)的字段名称有效。投影中未包含的字段无法在以后的聚合阶段中引用。以下清单显示了投影表达式的示例:

例子 95.投影表达式例子

// generates {$project: {name: 1, netPrice: 1}}
project("name", "netPrice")

// generates {$project: {thing1: $thing2}}
project().and("thing1").as("thing2")

// generates {$project: {a: 1, b: 1, thing2: $thing1}}
project("a","b").and("thing1").as("thing2")

例子 96.使用投影和排序的多阶段聚合

// generates {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}}
project("name", "netPrice"), sort(ASC, "name")

// generates {$project: {name: $firstname}}, {$sort: {name: 1}}
project().and("firstname").as("name"), sort(ASC, "name")

// does not work
project().and("firstname").as("name"), sort(ASC, "firstname")

有关项目操作的更多示例,请参见AggregationTests类。请注意,有关投影表达式的更多详细信息可以在 MongoDB Aggregation Framework 参考文档的corresponding section中找到。

10.11.4. 分面分类

从 3.4 版开始,MongoDB 通过使用 Aggregation Framework 支持多面分类。分面分类使用语义类别(常规类别或特定于主题的类别)进行组合,以创建完整的分类条目。流经聚合管道的文档分类为存储桶。多方面分类可在同一组 Importing 文档上进行各种聚合,而无需多次检索 Importing 文档。

Buckets

存储桶操作根据指定的表达式和存储桶边界将传入文档分类为称为存储桶的组。存储桶操作需要分组字段或分组表达式。您可以使用Aggregate类的bucket()bucketAuto()方法来定义它们。 BucketOperationBucketAutoOperation可以基于 Importing 文档的聚合表达式公开累积。您可以使用with…()方法和andOutput(String)方法通过 Fluent 的 API 使用其他参数扩展存储桶操作。您可以使用as(String)方法对操作进行别名。每个存储段在输出中均表示为文档。

BucketOperation定义了一组边界,以将传入文档分为以下类别。边界需要排序。以下清单显示了存储桶操作的一些示例:

例子 97.桶操作例子

// generates {$bucket: {groupBy: $price, boundaries: [0, 100, 400]}}
bucket("price").withBoundaries(0, 100, 400);

// generates {$bucket: {groupBy: $price, default: "Other" boundaries: [0, 100]}}
bucket("price").withBoundaries(0, 100).withDefault("Other");

// generates {$bucket: {groupBy: $price, boundaries: [0, 100], output: { count: { $sum: 1}}}}
bucket("price").withBoundaries(0, 100).andOutputCount().as("count");

// generates {$bucket: {groupBy: $price, boundaries: [0, 100], 5, output: { titles: { $push: "$title"}}}
bucket("price").withBoundaries(0, 100).andOutput("title").push().as("titles");

BucketAutoOperation确定边界,以尝试将文档平均分配到指定数量的存储桶中。 BucketAutoOperation可选地采用指定preferred number系列的粒度值,以确保所计算的边界边以首选的圆数或 10 的幂为结尾。以下清单显示了存储桶操作的示例:

例子 98.桶操作例子

// generates {$bucketAuto: {groupBy: $price, buckets: 5}}
bucketAuto("price", 5)

// generates {$bucketAuto: {groupBy: $price, buckets: 5, granularity: "E24"}}
bucketAuto("price", 5).withGranularity(Granularities.E24).withDefault("Other");

// generates {$bucketAuto: {groupBy: $price, buckets: 5, output: { titles: { $push: "$title"}}}
bucketAuto("price", 5).andOutput("title").push().as("titles");

要在存储桶中创建输出字段,存储桶操作可以使用AggregationExpressionandOutput()SpEL expressionsandOutputExpression()

请注意,有关存储桶表达式的更多详细信息可以在 MongoDB Aggregation Framework 参考文档的$bucket section$bucketAuto section中找到。

Multi-faceted Aggregation

多个聚合管道可用于创建多方面的聚合,这些聚合可表征单个聚合阶段中多个维度(或方面)上的数据。多方面的聚合提供了多种过滤器和分类,以指导数据浏览和分析。分面的一种常见实现方式是,有多少在线零售商通过对产品价格,制造商,尺寸和其他因素应用过滤器来提供缩小搜索结果范围的方法。

您可以使用Aggregation类的facet()方法来定义FacetOperation。您可以使用and()方法通过多个聚合管道自定义它。每个子管道在输出文档中都有自己的字段,其结果以文档数组的形式存储。

子管道可以在分组之前投影和过滤 Importing 文档。常见的用例包括在分类之前提取日期部分或计算。以下清单显示了方面操作示例:

例子 99.构面操作例子

// generates {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}}
facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice"))

// generates {$facet: {categorizedByCountry: [ { $match: { country: {$exists : true}}}, { $sortByCount: "$country"}]}}
facet(match(Criteria.where("country").exists(true)), sortByCount("country")).as("categorizedByCountry"))

// generates {$facet: {categorizedByYear: [
//     { $project: { title: 1, publicationYear: { $year: "publicationDate"}}},
//     { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}}
// ]}}
facet(project("title").and("publicationDate").extractYear().as("publicationYear"),
      bucketAuto("publicationYear", 5).andOutput("title").push().as("titles"))
  .as("categorizedByYear"))

请注意,有关构面操作的更多详细信息可以在 MongoDB Aggregation Framework 参考文档的$facet section中找到。

按计数排序

按计数排序操作根据指定表达式的值将传入文档分组,计算每个不同组中的文档计数,然后按计数对结果进行排序。使用Faceted Classification时,它提供了方便的快捷方式来应用排序。按计数排序操作需要分组字段或分组表达式。以下清单显示了按计数排序的示例:

例子 100.按计数例子排序

// generates { $sortByCount: "$country" }
sortByCount("country");

按计数排序操作等效于以下 BSON(二进制 JSON):

{ $group: { _id: <expression>, count: { $sum: 1 } } },
{ $sort: { count: -1 } }
投影表达式中的 Spring 表达式支持

我们支持通过ProjectionOperationBucketOperation类的andExpression方法在投影表达式中使用 SpEL 表达式。此功能使您可以将所需的表达式定义为 SpEL 表达式。在执行查询时,将 SpEL 表达式转换为相应的 MongoDB 投影表达式部分。这种安排使表达复杂的计算变得更加容易。

具有 SpEL 表达式的复杂计算

考虑以下 SpEL 表达式:

1 + (q + 1) / (q - 1)

前面的表达式转换为下面的投影表达式部分:

{ "$add" : [ 1, {
    "$divide" : [ {
        "$add":["$q", 1]}, {
        "$subtract":[ "$q", 1]}
    ]
}]}

您可以在聚合框架示例 5聚合框架示例 6的更多上下文中查看示例。您可以在SpelExpressionTransformerUnitTests中找到有关受支持的 SpEL 表达式构造的更多用法示例。下表显示了 Spring Data MongoDB 支持的 SpEL 转换:

表 8.支持的 SpEL 转换

SpEL Expression Mongo 表达部分
a == b { $eq : [$a, $b] }
a!= b { $ne : [$a , $b] }
a> b { $gt : [$a, $b] }
a> = b { $gte : [$a, $b] }
a <b { $lt : [$a, $b] }
a⇐b { $lte : [$a, $b] }
b { $add : [$a, $b] }
a-b { $subtract : [$a, $b] }
a * b { $multiply : [$a, $b] }
a/b { $divide : [$a, $b] }
a^b { $pow : [$a, $b] }
a%b { $mod : [$a, $b] }
a && b { $and : [$a, $b] }
b { $or : [$a, $b] }
!a { $not : [$a] }

除了上表中显示的转换之外,您还可以使用诸如new之类的标准 SpEL 操作来(例如)通过数组和引用表达式的名称来创建数组和引用表达式(后跟要放在方括号中使用的参数)。下面的示例演示如何以这种方式创建数组:

// { $setEquals : [$a, [5, 8, 13] ] }
.andExpression("setEquals(a, new int[]{5, 8, 13})");
聚合框架示例

本节中的示例演示了带有 Spring Data MongoDB 的 MongoDB 聚合框架的使用模式。

聚合框架示例 1

在这个介绍性示例中,我们希望聚合标签列表,以从 MongoDB 集合(称为tags)中获取特定标签的出现次数,该集合按出现次数降序排列。本示例演示了分组,排序,投影(选择)和展开(结果拆分)的用法。

class TagCount {
 String tag;
 int n;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

Aggregation agg = newAggregation(
    project("tags"),
    unwind("tags"),
    group("tags").count().as("n"),
    project("n").and("tag").previousOperation(),
    sort(DESC, "n")
);

AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, "tags", TagCount.class);
List<TagCount> tagCount = results.getMappedResults();

上面的清单使用以下算法:

  • 使用newAggregation静态工厂方法创建一个新的聚合,我们将聚合操作列表传递给该方法。这些聚合操作定义了Aggregation的聚合管道。

  • 使用project操作从 Importing 集合中选择tags字段(是字符串数组)。

  • 使用unwind操作可为tags数组中的每个标签生成一个新文档。

  • 使用group操作为每个tags值定义一个组,我们将为其汇总出现次数(通过使用count聚合运算符并将结果收集到称为n的新字段中)。

  • 选择n字段,并为从上一个组操作(因此调用previousOperation())生成的 ID 字段创建别名,名称为tag

  • 使用sort操作可以按照标签的出现次数降序对结果标签列表进行排序。

  • MongoTemplate上调用aggregate方法,以将创建的Aggregation作为参数,让 MongoDB 执行实际的聚合操作。

请注意,将 Importing 集合显式指定为aggregate方法的tags参数。如果未明确指定 Importing 集合的名称,则它将从作为第一个参数传递给newAggreation方法的 Importing 类派生。

聚合框架示例 2

本示例基于 MongoDB Aggregation Framework 文档中的各 State 最大和最小城市示例。我们添加了其他排序,以使用不同的 MongoDB 版本产生稳定的结果。在这里,我们要使用汇总框架返回每个 State 的人口最小和最大的城市。本示例演示了分组,排序和投影(选择)。

class ZipInfo {
   String id;
   String city;
   String state;
   @Field("pop") int population;
   @Field("loc") double[] location;
}

class City {
   String name;
   int population;
}

class ZipInfoStats {
   String id;
   String state;
   City biggestCity;
   City smallestCity;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class,
    group("state", "city")
       .sum("population").as("pop"),
    sort(ASC, "pop", "state", "city"),
    group("state")
       .last("city").as("biggestCity")
       .last("pop").as("biggestPop")
       .first("city").as("smallestCity")
       .first("pop").as("smallestPop"),
    project()
       .and("state").previousOperation()
       .and("biggestCity")
          .nested(bind("name", "biggestCity").and("population", "biggestPop"))
       .and("smallestCity")
          .nested(bind("name", "smallestCity").and("population", "smallestPop")),
    sort(ASC, "state")
);

AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class);
ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0);

请注意,ZipInfo类 Map 给定 Importing 集合的结构。 ZipInfoStats类以所需的输出格式定义结构。

前面的清单使用以下算法:

  • 使用group操作从 Importing 集合中定义一个组。分组条件是statecity字段的组合,形成了组的 ID 结构。我们使用sum运算符从分组的元素中聚合population属性的值,并将结果保存在pop字段中。

  • 使用sort操作以popstatecity字段按升序对中间结果进行排序,以使最小城市在结果的顶部,最大城市在结果的底部。请注意,对statecity的排序是针对组 ID 字段(Spring Data MongoDB 处理的)隐式执行的。

  • 再次使用group操作将中间结果按state分组。请注意,state再次隐式引用了组 ID 字段。我们分别在project操作中分别调用last(…)first(…)运算符来选择最大和最小城市的名称和人口数。

  • 从上一个group操作中选择state字段。请注意,state再次隐式引用了组 ID 字段。由于我们不希望显示隐式生成的 ID,因此可以使用and(previousOperation()).exclude()将 ID 从上一个操作中排除。因为我们要在输出类中填充嵌套的City结构,所以必须使用嵌套的方法发出适当的子文档。

  • sort操作中,按其状态名称升序对StateStats的结果列表进行排序。

请注意,我们从作为第一个参数传递给newAggregation方法的ZipInfo类派生了 Importing 集合的名称。

聚合框架示例 3

本示例基于 MongoDB Aggregation Framework 文档中的人口超过 1000 万的 State示例。我们添加了其他排序,以使用不同的 MongoDB 版本产生稳定的结果。在这里,我们希望使用汇总框架返回人口超过 1000 万的所有 State。本示例演示了分组,排序和匹配(过滤)。

class StateStats {
   @Id String id;
   String state;
   @Field("totalPop") int totalPopulation;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<ZipInfo> agg = newAggregation(ZipInfo.class,
    group("state").sum("population").as("totalPop"),
    sort(ASC, previousOperation(), "totalPop"),
    match(where("totalPop").gte(10 * 1000 * 1000))
);

AggregationResults<StateStats> result = mongoTemplate.aggregate(agg, StateStats.class);
List<StateStats> stateStatsList = result.getMappedResults();

前面的清单使用以下算法:

  • 将 Importing 集合按state字段分组,并计算population字段的总和,并将结果存储在新字段"totalPop"中。

  • "totalPop"字段外,还按上一组操作的 id 引用对中间结果进行排序。

  • 通过使用接受Criteria查询作为参数的match操作来过滤中间结果。

请注意,我们从作为第一个参数传递给newAggregation方法的ZipInfo类派生了 Importing 集合的名称。

聚合框架示例 4

此示例演示了投影操作中简单算术运算的使用。

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .and("netPrice").plus(1).as("netPricePlus1")
        .and("netPrice").minus(1).as("netPriceMinus1")
        .and("netPrice").multiply(1.19).as("grossPrice")
        .and("netPrice").divide(2).as("netPriceDiv2")
        .and("spaceUnits").mod(2).as("spaceUnitsMod2")
);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();

请注意,我们从作为第一个参数传递给newAggregation方法的Product类派生了 Importing 集合的名称。

聚合框架示例 5

此示例演示了在投影操作中使用从 SpEL 表达式派生的简单算术运算。

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .andExpression("netPrice + 1").as("netPricePlus1")
        .andExpression("netPrice - 1").as("netPriceMinus1")
        .andExpression("netPrice / 2").as("netPriceDiv2")
        .andExpression("netPrice * 1.19").as("grossPrice")
        .andExpression("spaceUnits % 2").as("spaceUnitsMod2")
        .andExpression("(netPrice * 0.8  + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge")

);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();
聚合框架示例 6

本示例演示了在投影操作中使用从 SpEL 表达式派生的复杂算术运算。

注意:传递给addExpression方法的其他参数可以根据索引器表达式的位置进行引用。在此示例中,我们使用[0]引用了参数数组的第一个参数。当 SpEL 表达式转换为 MongoDB 聚合框架表达式时,外部参数表达式将替换为其各自的值。

class Product {
    String id;
    String name;
    double netPrice;
    int spaceUnits;
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

double shippingCosts = 1.2;

TypedAggregation<Product> agg = newAggregation(Product.class,
    project("name", "netPrice")
        .andExpression("(netPrice * (1-discountRate)  + [0]) * (1+taxRate)", shippingCosts).as("salesPrice")
);

AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
List<Document> resultList = result.getMappedResults();

请注意,我们还可以在 SpEL 表达式内引用文档的其他字段。

聚合框架示例 7

本示例使用条件投影。它是从$ cond 参考文档派生的。

public class InventoryItem {

  @Id int id;
  String item;
  String description;
  int qty;
}

public class InventoryItemProjection {

  @Id int id;
  String item;
  String description;
  int qty;
  int discount
}
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
  project("item").and("discount")
    .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250))
      .then(30)
      .otherwise(20))
    .and(ifNull("description", "Unspecified")).as("description")
);

AggregationResults<InventoryItemProjection> result = mongoTemplate.aggregate(agg, "inventory", InventoryItemProjection.class);
List<InventoryItemProjection> stateStatsList = result.getMappedResults();

此一步聚合使用inventory集合的投影操作。我们通过对所有qty大于或等于250的库存物料使用条件操作来投影discount字段。对description字段执行第二个条件投影。我们将Unspecified描述应用于没有description字段的所有项目或具有null描述的项目。

从 MongoDB 3.6 开始,可以使用条件表达式从投影中排除字段。

例子 101.条件聚合投影

TypedAggregation<Book> agg = Aggregation.newAggregation(Book.class,
  project("title")
    .and(ConditionalOperators.when(ComparisonOperators.valueOf("author.middle")     (1)
        .equalToValue(""))                                                          (2)
        .then("$$REMOVE")                                                           (3)
        .otherwiseValueOf("author.middle")                                          (4)
    )
	.as("author.middle"));
  • (1) 如果字段author.middle的值
  • (2) 不包含值,
  • (3) ,然后使用$$REMOVE排除该字段。
  • (4) 否则,添加字段值author.middle

10.11.5. 自定义转化-覆盖默认 Map

影响 Map 结果的最简单的方法是通过@FieldComments 指定所需的本机 MongoDB 目标类型。这允许在域模型中使用非 MongoDB 类型(例如BigDecimal),同时以本机org.bson.types.Decimal128格式保留值。

例子 102.显式目标类型 Map

public class Payment {

  @Id String id; (1)

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

  Date date; (3)

}
{
  "_id"   : ObjectId("5ca4a34fa264a01503b36af8"), (1)
  "value" : NumberDecimal(2.099), (2)
  "date"   : ISODate("2019-04-03T12:11:01.870Z") (3)
}
  • (1) 代表有效ObjectId的字符串* id *值会自动转换。有关详情,请参见_id 字段在 Map 层中的处理方式
  • (2) 所需的目标类型明确定义为Decimal128,它转换为NumberDecimal。否则,BigDecimal值将被转换为String
  • (3) Date值由 MongoDB 驱动程序本身处理,并存储为ISODate

上面的代码片段很容易提供简单的类型提示。为了获得对 Map 过程的更细粒度的控制,您可以向MongoConverter实现注册 Spring 转换器,例如MappingMongoConverter

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

Note

有关 Spring 类型转换服务的更多信息,请参见参考文档here

使用注册的 Spring 转换器进行保存

以下示例显示了Converter的实现,该实现从Person对象转换为org.bson.Document

import org.springframework.core.convert.converter.Converter;

import 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;
  }
}
使用 Spring 转换器阅读

以下示例显示了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;
  }
}
在 MongoConverter 中注册 Spring 转换器

Mongo Spring 命名空间提供了一种方便的方式来向MappingMongoConverter注册 Spring Converter实例。以下配置片段显示了如何手动注册转换器 bean 以及将MappingMongoConverter包装到MongoTemplate中:

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

<mongo:mapping-converter>
  <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"/>

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
  <constructor-arg name="mongoConverter" ref="mappingConverter"/>
</bean>

您还可以使用custom-converters元素的base-package属性为给定包下方的所有ConverterGenericConverter实现启用 Classpath 扫描,如以下示例所示:

<mongo:mapping-converter>
  <mongo:custom-converters base-package="com.acme.**.converters" />
</mongo:mapping-converter>
Converter Disambiguation

通常,我们会检查Converter实现的来源和目标类型,并将它们转换为源和目标。根据其中之一是 MongoDB 可以本地处理的类型,我们将转换器实例注册为读取或写入转换器。以下示例显示了写转换器和读转换器(请注意,区别在于Converter上的限定符 Sequences):

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

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

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

10.12. 索引和集合 Management

MongoTemplate提供了一些用于 Management 索引和集合的方法。这些方法被收集到名为IndexOperations的帮助程序界面中。您可以通过调用indexOps方法并传入您的实体的集合名称或java.lang.Class(集合名称是从.class派生的,无论是按名称还是从 Comments 元数据)来访问这些操作。

以下清单显示了IndexOperations界面:

public interface IndexOperations {

  void ensureIndex(IndexDefinition indexDefinition);

  void dropIndex(String name);

  void dropAllIndexes();

  void resetIndexCache();

  List<IndexInfo> getIndexInfo();
}

10.12.1. 创建索引的方法

您可以使用 MongoTemplate 类在集合上创建索引以提高查询性能,如以下示例所示:

mongoTemplate.indexOps(Person.class).ensureIndex(new Index().on("name",Order.ASCENDING));

ensureIndex确保为集合存在提供的 IndexDefinition 的索引。

您可以使用IndexDefinitionGeoSpatialIndexTextIndexDefinition类来创建标准索引,地理空间索引和文本索引。例如,给定上一节中定义的Venue类,您可以声明地理空间查询,如以下示例所示:

mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));

Note

IndexGeospatialIndex支持collations的配置。

10.12.2. 访问索引信息

IndexOperations接口具有getIndexInfo方法,该方法返回IndexInfo个对象的列表。该列表包含在集合上定义的所有索引。下面的示例在Person类上定义一个具有age属性的索引:

template.indexOps(Person.class).ensureIndex(new Index().on("age", Order.DESCENDING).unique());

List<IndexInfo> indexInfoList = template.indexOps(Person.class).getIndexInfo();

// Contains
// [IndexInfo [fieldSpec={_id=ASCENDING}, name=_id_, unique=false, sparse=false],
//  IndexInfo [fieldSpec={age=DESCENDING}, name=age_-1, unique=true, sparse=false]]

10.12.3. 使用集合的方法

以下示例显示如何创建集合:

例子 103.使用MongoTemplate来处理集合

MongoCollection<Document> collection = null;
if (!mongoTemplate.getCollectionNames().contains("MyNewCollection")) {
    collection = mongoTemplate.createCollection("MyNewCollection");
}

mongoTemplate.dropCollection("MyNewCollection");
  • getCollectionNames :返回一组集合名称。

  • collectionExists :检查是否存在具有给定名称的集合。

  • createCollection :创建一个无上限的集合。

  • dropCollection :删除集合。

  • getCollection :按名称获取一个集合,如果不存在则创建一个集合。

Note

集合创建允许使用CollectionOptions进行自定义并支持collations

10.13. 执行命令

您可以使用MongoTemplate上的executeCommand(…)方法来获取 MongoDB 驱动程序的MongoDatabase.runCommand( )方法。这些方法还可以将异常转换为 Spring 的DataAccessException层次结构。

10.13.1. 执行命令的方法

  • Document executeCommand (Document command):运行 MongoDB 命令。

  • Document executeCommand (Document command, ReadPreference readPreference):使用给定的可空 MongoDB ReadPreference运行 MongoDB 命令。

  • Document executeCommand (String jsonCommand):执行以 JSON 字符串表示的 MongoDB 命令。

10.14. 生命周期事件

MongoDBMap 框架包括几个org.springframework.context.ApplicationEvent事件,您的应用程序可以通过在ApplicationContext中注册特殊 bean 来响应。基于 Spring 的ApplicationContext事件基础架构,其他产品(例如 Spring Integration)可以轻松接收这些事件,因为它们是基于 Spring 的应用程序中众所周知的事件机制。

要在对象进行转换过程(将您的域对象转换为org.bson.Document)之前对其进行拦截,可以注册一个AbstractMongoEventListener的子类,该子类将覆盖onBeforeConvert方法。调度事件后,将在域对象进入转换器之前调用并传递您的侦听器。以下示例显示了如何执行此操作:

public class BeforeConvertListener extends AbstractMongoEventListener<Person> {
  @Override
  public void onBeforeConvert(BeforeConvertEvent<Person> event) {
    ... does some auditing manipulation, set timestamps, whatever ...
  }
}

要在对象进入数据库之前对其进行拦截,可以注册一个覆盖onBeforeSave方法的org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener子类。调度事件后,将调用您的侦听器并传递该域对象和转换后的com.mongodb.Document。以下示例显示了如何执行此操作:

public class BeforeSaveListener extends AbstractMongoEventListener<Person> {
  @Override
  public void onBeforeSave(BeforeSaveEvent<Person> event) {
    … change values, delete them, whatever …
  }
}

在 Spring ApplicationContext 中声明这些 bean 会使事件在分派时被调用。

AbstractMappingEventListener中存在以下回调方法:

  • onBeforeConvert:在MongoConverter将对象转换为Document之前,调用MongoTemplate insertinsertListsave操作。

  • onBeforeSave:在将Document插入或保存在数据库中之前,请在MongoTemplate insertinsertListsave操作中调用。

  • onAfterSave:在 Document插入或保存在数据库中后MongoTemplate insertinsertListsave操作中调用。

  • onAfterLoad:从数据库中检索到Document后,在MongoTemplate findfindAndRemovefindOnegetCollection方法中调用。

  • onAfterConvert:从数据库中检索到Document之后,在MongoTemplate findfindAndRemovefindOnegetCollection方法中调用了该方法,将其转换为 POJO。

Note

仅针对根级别类型发出生命周期事件。除非它们是用@DBRefComments 的文档引用,否则用作文档根目录中的属性的复杂类型将不受事件发布的约束。

Warning

生命周期事件取决于ApplicationEventMulticaster,在SimpleApplicationEventMulticaster的情况下可以使用TaskExecutor进行配置,因此生命周期事件在处理事件时不提供任何保证。

10.15. 实体回调

Spring Data 基础结构提供了用于在调用某些方法之前和之后修改实体的钩子。那些所谓的EntityCallback实例提供了一种方便的方法,可以以回调方式检查和潜在地修改实体。
EntityCallback看起来很像专门的ApplicationListener。一些 Spring Data 模块发布存储特定事件(例如BeforeSaveEvent),这些事件允许修改给定实体。在某些情况下,例如使用不可变类型时,这些事件可能会引起麻烦。同样,事件发布依赖于ApplicationEventMulticaster。如果使用异步TaskExecutor进行配置,则可能导致不可预测的结果,因为事件处理可以分叉到线程上。

实体回调为同步和反应式 API 提供集成点,以确保在处理链中定义明确的检查点处按 Sequences 执行,返回可能修改的实体或反应式包装器类型。

实体回调通常按 API 类型分开。这种分离意味着同步 API 仅考虑同步实体回调,而反应式实现仅考虑反应实体回调。

Note

实体回调 API 已随 Spring Data Commons 2.2 引入。这是应用实体修改的推荐方法。在调用可能已注册的EntityCallback实例之前,**仍在发布现有的特定 StoreApplicationEvents

10.15.1. 实施实体回调

EntityCallback通过其泛型类型参数直接与其域类型相关联。每个 Spring Data 模块通常附带一组涵盖实体生命周期的 sched 义EntityCallback接口。

例子 104. EntityCallback的解剖

@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked before a domain object is saved.
	 * Can return either the same or a modified instance.
	 *
	 * @return the domain object to be persisted.
	 */
	T onBeforeSave(T entity <2>, String collection <3>); (1)
}
  • (1) BeforeSaveCallback在保存实体之前要调用的特定方法。返回一个可能被修改的实例。
  • (2) 实体在保留之前。
  • (3) 多个存储特定参数,例如实体所持久存储的* collection *。

例子 105.ReactiveEntityCallback的解剖

@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked on subscription, before a domain object is saved.
	 * The returned Publisher can emit either the same or a modified instance.
	 *
	 * @return Publisher emitting the domain object to be persisted.
	 */
	Publisher<T> onBeforeSave(T entity <2>, String collection <3>); (1)
}
  • (1) BeforeSaveCallback在保存实体之前在订阅上调用的特定方法。发出可能已修改的实例。
  • (2) 实体在保留之前。
  • (3) 多个存储特定参数,例如实体所持久存储的* collection *。

Note

可选的实体回调参数由实现中的 Spring Data 模块定义,并从EntityCallback.callback()的调用站点推断出。

实施适合您的应用程序需求的接口,如下例所示:

例子 106.例子BeforeSaveCallback

class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {      (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {                   (1)

		if(collection == "user") {
		    return // ...
		}

		return // ...
	}

	@Override
	public int getOrder() {
		return 100;                                                                  (2)
	}
}
  • (1) 根据您的要求实现回调。
  • (2) 如果存在相同域类型的多个实体回调,则可能对实体回调进行排序。排序遵循最低优先级。

10.15.2. 注册实体回调

EntityCallback bean 由 Store 特定的实现提取,以防它们在ApplicationContext中注册。大多数模板 API 已经实现ApplicationContextAware,因此可以访问ApplicationContext

以下示例说明了有效的实体回调注册的集合:

例子 107.例子EntityCallback Bean 注册

@Order(1)                                                           (1)
@Component
class First implements BeforeSaveCallback<Person> {

	@Override
	public Person onBeforeSave(Person person) {
		return // ...
	}
}

@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
                                                           Ordered { (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {
		// ...
	}

	@Override
	public int getOrder() {
		return 100;                                                  (2)
	}
}

@Configuration
public class EntityCallbackConfiguration {

    @Bean
    BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {   (3)
        return (BeforeSaveCallback<Person>) it -> // ...
    }
}

@Component
class UserCallbacks implements BeforeConvertCallback<User>,
                                        BeforeSaveCallback<User> {   (4)

	@Override
	public Person onBeforeConvert(User user) {
		return // ...
	}

	@Override
	public Person onBeforeSave(User user) {
		return // ...
	}
}
  • (1) BeforeSaveCallback@OrderComments 中接收其命令。
  • (2) BeforeSaveCallback通过Ordered接口实现接收订单。
  • (3) BeforeSaveCallback使用 lambda 表达式。默认情况下无序,最后调用。
  • (4) 将多个实体回调接口组合在一个实现类中。

10.15.3. 存储特定的 EntityCallbacks

Spring Data MongoDB 使用EntityCallback API 进行审核,并对以下回调进行响应。

表 9.支持的实体回调

Callback Method Description Order
Reactive/BeforeConvertCallback onBeforeConvert(T entity, String collection) 在将域对象转换为org.bson.Document之前调用。 Ordered.LOWEST_PRECEDENCE
Reactive/AuditingEntityCallback onBeforeConvert(Object entity, String collection) 标记“已创建”或“已修改”的可审核实体 100
Reactive/BeforeSaveCallback onBeforeSave(T entity, org.bson.Document target, String collection) 在保存域对象之前调用。

可以修改要保留的目标Document,其中包含所有 Map 的实体信息。 Ordered.LOWEST_PRECEDENCE

10.16. exception 翻译

Spring 框架为多种数据库和 Map 技术提供了异常转换。传统上,这是针对 JDBC 和 JPA 的。 Spring 对 MongoDB 的支持通过提供org.springframework.dao.support.PersistenceExceptionTranslator接口的实现将该功能扩展到 MongoDB 数据库。

Map 到 Spring 的一致的数据访问异常层次结构的动机是,您随后可以编写可移植的描述性异常处理代码,而无需诉诸针对 MongoDB 错误代码的编码。 Spring 的所有数据访问异常均从根DataAccessException类继承,因此您可以确保在单个 try-catch 块中捕获所有与数据库相关的异常。请注意,并非 MongoDB 驱动程序引发的所有异常都继承自MongoException类。内部异常和消息被保留,因此不会丢失任何信息。

MongoExceptionTranslator执行的某些 Map 是com.mongodb.Network to DataAccessResourceFailureExceptionMongoException错误代码 1003、12001、12010、12011 和 12012 到InvalidDataAccessApiUsageException。查看实现以获取有关 Map 的更多详细信息。

10.17. 执行回调

所有 Spring 模板类的共同设计 Feature 是所有功能都路由到模板的 execute 回调方法之一中。这样做有助于确保异常和可能需要的任何资源 Management 得到一致执行。尽管 JDBC 和 JMS 比 MongoDB 更加需要此功能,但它仍为发生异常转换和日志记录提供了一个场所。因此,使用这些 execute 回调是访问 MongoDB 驱动程序的MongoDatabaseMongoCollection对象以执行未作为MongoTemplate方法公开的不常见操作的首选方法。

下表描述了 execute 回调方法。

  • <T> T execute (Class<?> entityClass, CollectionCallback<T> action):对指定类的实体集合执行给定的CollectionCallback

  • <T> T execute (String collectionName, CollectionCallback<T> action):对给定名称的集合执行给定CollectionCallback

  • <T> T execute (DbCallback<T> action):执行 DbCallback 并根据需要翻译任何异常。 Spring Data MongoDB 提供对 2.2 版中引入的 MongoDB 的聚合框架的支持。

  • <T> T execute (String collectionName, DbCallback<T> action):在给定名称的集合上执行DbCallback,翻译任何必要的异常。

  • <T> T executeInSession (DbCallback<T> action):在与数据库相同的连接内执行给定的DbCallback,以确保在可能读取大量写入数据的大量写入环境中保持一致性。

下面的示例使用CollectionCallback返回有关索引的信息:

boolean hasIndex = template.execute("geolocation", new CollectionCallbackBoolean>() {
  public Boolean doInCollection(Venue.class, DBCollection collection) throws MongoException, DataAccessException {
    List<Document> indexes = collection.getIndexInfo();
    for (Document document : indexes) {
      if ("location_2d".equals(document.get("name"))) {
        return true;
      }
    }
    return false;
  }
});

10.18. GridFS 支持

MongoDB 支持在其文件系统 GridFS 中存储二进制文件。 Spring Data MongoDB 提供了一个GridFsOperations接口以及相应的实现GridFsTemplate,以使您与文件系统进行交互。您可以将MongoDbFactoryMongoConverter递给GridFsTemplate实例,如下例所示:

例子 108. GridFsTemplate 的 JavaConfig 设置

class GridFsConfiguration extends AbstractMongoConfiguration {

  // … further configuration omitted

  @Bean
  public GridFsTemplate gridFsTemplate() {
    return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
  }
}

相应的 XML 配置如下:

例子 109. GridFsTemplate 的 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: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.xsd">

  <mongo:db-factory id="mongoDbFactory" dbname="database" />
  <mongo:mapping-converter id="converter" />

  <bean class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
    <constructor-arg ref="mongoDbFactory" />
    <constructor-arg ref="converter" />
  </bean>

</beans>

现在可以将模板注入并用于执行存储和检索操作,如以下示例所示:

例子 110.使用 GridFsTemplate 来存储文件

class GridFsClient {

  @Autowired
  GridFsOperations operations;

  @Test
  public void storeFileToGridFs() {

    FileMetadata metadata = new FileMetadata();
    // populate metadata
    Resource file = … // lookup File or Resource

    operations.store(file.getInputStream(), "filename.txt", metadata);
  }
}

store(…)操作采用InputStream,文件名和(可选)有关要存储的文件的元数据信息。元数据可以是任意对象,由配置为GridFsTemplateMongoConverter编组。或者,您也可以提供Document

您可以通过find(…)getResources(…)方法从文件系统读取文件。首先让我们看一下find(…)方法。您可以找到一个或多个与Query匹配的文件。您可以使用GridFsCriteria helper 类来定义查询。它提供了静态工厂方法来封装默认的元数据字段(例如whereFilename()whereContentType())或自定义字段到whereMetaData()。下面的示例显示如何使用GridFsTemplate查询文件:

例子 111.使用 GridFsTemplate 查询文件

class GridFsClient {

  @Autowired
  GridFsOperations operations;

  @Test
  public void findFilesInGridFs() {
    GridFSFindIterable result = operations.find(query(whereFilename().is("filename.txt")))
  }
}

Note

当前,从 GridFS 检索文件时,MongoDB 不支持定义排序条件。出于这个原因,将忽略在传递给find(…)方法的Query实例上定义的任何排序条件。

从 GridF 读取文件的另一种方法是使用ResourcePatternResolver接口引入的方法。它们允许将 Ant 路径传递给方法,因此可以检索与给定模式匹配的文件。下面的示例演示如何使用GridFsTemplate读取文件:

例子 112.使用 GridFsTemplate 读取文件

class GridFsClient {

  @Autowired
  GridFsOperations operations;

  @Test
  public void readFilesFromGridFs() {
    GridFsResources[] txtFiles = operations.getResources("*.txt");
  }
}

GridFsOperations扩展了ResourcePatternResolver,并允许将GridFsTemplate(例如)插入ApplicationContext,以从 MongoDB 数据库读取 Spring Config 文件。

10.19. 带有尾光标的无限流

默认情况下,当 Client 端用尽游标提供的所有结果时,MongoDB 会自动关闭游标。在耗尽时关闭游标会将流变成有限流。对于capped collections,您可以使用Tailable Cursor,该Tailable Cursor在 Client 端使用完所有最初返回的数据后保持打开状态。

Tip

上限集合可以使用MongoOperations.createCollection创建。为此,请提供所需的CollectionOptions.empty().capped()…

命令式和反应式 MongoDB API 均可使用可尾游标。强烈建议使用反应式变量,因为它消耗的资源较少。但是,如果您不能使用反应式 API,您仍然可以使用 Spring 生态系统中已经很普遍的消息传递概念。

10.19.1. 具有 MessageListener 的可调节游标

使用“同步驱动程序”侦听有上限的集合会创建一个长期运行的阻塞任务,需要将其委派给单独的组件。在这种情况下,我们需要首先创建一个MessageListenerContainer,它将成为运行特定SubscriptionRequest的主要入口点。 Spring Data MongoDB 已经提供了默认实现,该实现可在MongoTemplate上运行,并且能够为TailableCursorRequest创建和执行Task实例。

以下示例显示了如何对MessageListener个实例使用可尾游标:

例子 113.具有MessageListener个实例的可尾游标

MessageListenerContainer container = new DefaultMessageListenerContainer(template);
container.start();                                                                  (1)

MessageListener<Document, User> listener = System.out::println;                     (2)

TailableCursorRequest request = TailableCursorRequest.builder()
  .collection("orders")                                                             (3)
  .filter(query(where("value").lt(100)))                                            (4)
  .publishTo(listener)                                                              (5)
  .build();

container.register(request, User.class);                                            (6)

// ...

container.stop();                                                                   (7)
  • (1) 启动容器会初始化资源,并为已注册的SubscriptionRequest实例启动Task实例。启动后添加的请求将立即运行。
  • (2) 定义接收到Message时调用的侦听器。 Message#getBody()转换为请求的域类型。使用Document无需转换即可接收原始结果。
  • (3) 设置要收听的收藏集。
  • (4) 提供用于接收文档的可选过滤器。
  • (5) 设置消息侦听器以将传入的Message s 发布到。
  • (6) 注册请求。返回的Subscription可用于检查当前Task状态并取消其执行以释放资源。
  • (7) 一旦确定您不再需要停止容器,请不要忘记停止它。这样做会停止容器中所有正在运行的Task实例。

10.19.2. 反应式尾标

通过将尾部游标与 Reactive 数据类型一起使用,可以构造无限的流。可拖尾游标保持打开状态,直到从外部关闭。当新文档到达加盖的集合中时,它将发出数据。

如果查询未返回匹配项,或者光标在集合的“末尾”返回了文档,然后应用程序删除了该文档,那么有尾游标可能会变为无效或无效。以下示例显示如何创建和使用无限流查询:

例子 114.使用 ReactiveMongoOperations 的无限流查询

Flux<Person> stream = template.tail(query(where("name").is("Joe")), Person.class);

Disposable subscription = stream.doOnNext(person -> System.out.println(person)).subscribe();

// …

// Later: Dispose the subscription to close the stream
subscription.dispose();

Spring Data MongoDB Reactive 存储库通过使用@TailableComments 查询方法来支持无限流。这适用于返回Flux和其他能够发出多个元素的反应类型的方法,如以下示例所示:

例子 115.使用 ReactiveMongoRepository 的无限流查询

public interface PersonRepository extends ReactiveMongoRepository<Person, String> {

  @Tailable
  Flux<Person> findByFirstname(String firstname);

}

Flux<Person> stream = repository.findByFirstname("Joe");

Disposable subscription = stream.doOnNext(System.out::println).subscribe();

// …

// Later: Dispose the subscription to close the stream
subscription.dispose();

10.20. 更改流

从 MongoDB 3.6 开始,Change Streams使应用程序收到有关更改的通知,而不必拖延操作日志。

Note

变更流支持仅适用于副本集或分片群集。

强制性和响应性 MongoDB Java 驱动程序都可以使用变更流。强烈建议使用反应式变量,因为它消耗的资源较少。但是,如果您不能使用反应式 API,您仍然可以通过使用 Spring 生态系统中已经很普遍的消息传递概念来获取变更事件。

既可以在集合级别上也可以在数据库级别上观看,而数据库级别的变体可以发布数据库中所有集合的更改。订阅数据库更改流时,请确保为事件类型使用合适的类型,因为转换可能不适用于不同的实体类型。如有疑问,请使用Document

10 0.20.1. 使用 MessageListener 更改流

监听通过使用同步驱动程序更改流会创建长期运行的阻塞任务,需要将其委派给单独的组件。在这种情况下,我们需要首先创建一个MessageListenerContainer,它将是运行特定SubscriptionRequest任务的主要入口点。 Spring Data MongoDB 已经提供了默认实现,该实现可在MongoTemplate上运行,并且能够为ChangeStreamRequest创建和执行Task实例。

以下示例说明如何对MessageListener个实例使用更改流:

例子 116.用MessageListener个实例改变流

MessageListenerContainer container = new DefaultMessageListenerContainer(template);
container.start();                                                                                        (1)

MessageListener<ChangeStreamDocument<Document>, User> listener = System.out::println;                     (2)
ChangeStreamRequestOptions options = new ChangeStreamRequestOptions("user", ChangeStreamOptions.empty()); (3)

Subscription subscription = container.register(new ChangeStreamRequest<>(listener, options), User.class); (4)

// ...

container.stop();                                                                                         (5)
  • (1) 启动容器将初始化资源,并为已注册的SubscriptionRequest实例启动Task实例。启动后添加的请求将立即运行。
  • (2) 定义接收到Message时调用的侦听器。 Message#getBody()转换为请求的域类型。使用Document无需转换即可接收原始结果。
  • (3) 设置收藏集以通过ChangeStreamOptions收听并提供其他选项。
  • (4) 注册请求。返回的Subscription可用于检查当前Task状态并取消其执行以释放资源。
  • (5) 确定您不再需要容器后,请不要忘记停止它。这样做会停止容器中所有正在运行的Task实例。

Note

处理时的错误将传递给org.springframework.util.ErrorHandler。除非另有说明,否则默认情况下会应用附加ErrorHandler的日志。
请使用register(request, body, errorHandler)提供其他功能。

10 0.20.2. Reactive 变化流

使用反应式 API 订阅变更流是使用流的一种更自然的方法。不过,基本的构建基块(例如ChangeStreamOptions)保持不变。以下示例显示如何使用发出ChangeStreamEvent的变更流:

例子 117.改变运行ChangeStreamEvent的流

Flux<ChangeStreamEvent<User>> flux = reactiveTemplate.changeStream(User.class) (1)
    .watchCollection("people")
    .filter(where("age").gte(38))                                              (2)
    .listen();                                                                 (3)
  • (1) 基础文档应转换为的事件目标类型。忽略此项以接收未经转换的原始结果。
  • (2) 使用聚合管道或仅查询Criteria来过滤事件。
  • (3) 获得Flux个变更流事件。 ChangeStreamEvent#getBody()从(2)转换为请求的域类型。

10 .20.3. 恢复变更流

可以恢复更改流,并在您离开的地方 continue 发送事件。要恢复流,您需要提供恢复令牌或最近的服务器时间(以 UTC 为单位)。使用ChangeStreamOptions相应地设置值。

以下示例显示如何使用服务器时间设置恢复偏移:

例子 118.恢复变更流

Flux<ChangeStreamEvent<User>> resumed = template.changeStream(User.class)
    .watchCollection("people")
    .resumeAt(Instant.now().minusSeconds(1)) (1)
    .listen();
  • (1) 您可以通过getTimestamp方法获得ChangeStreamEvent的服务器时间,也可以使用通过getResumeToken公开的resumeToken

Tip

在某些情况下,恢复变更流时Instant可能不够精确。为此,请使用 MongoDB 本机BsonTimestamp

11. MongoDB 会话

从 3.6 版开始,MongoDB 支持会话的概念。会话的使用启用了 MongoDB 的Causal Consistency模型,该模型保证以尊重其因果关系的 Sequences 运行操作。这些实例分为ServerSession个实例和ClientSession个实例。在本节中,当我们谈论会话时,我们指的是ClientSession

Warning

Client 端会话内的操作与会话外的操作不是隔离的。

MongoOperationsReactiveMongoOperations都提供了将ClientSession绑定到操作的网关方法。 MongoCollectionMongoDatabase使用实现 MongoDB 的集合和数据库接口的会话代理对象,因此您无需在每次调用时添加会话。这意味着对MongoCollection#find()的潜在调用被委派给MongoCollection#find(ClientSession)

Note

诸如(Reactive)MongoOperations#getCollection之类的方法返回本机 MongoDB Java 驱动程序网关对象(例如MongoCollection),它们本身为ClientSession提供专用方法。这些方法不是会话会话代理。直接与MongoCollectionMongoDatabase进行交互时,而不是通过MongoOperations上的#execute回调之一,应在需要的地方提供ClientSession

11.1. 同步 ClientSession 支持。

以下示例显示了会话的用法:

例子 119. ClientSessionMongoOperations

ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
    .causallyConsistent(true)
    .build();

ClientSession session = client.startSession(sessionOptions); (1)

template.withSession(() -> session)
    .execute(action -> {

        Query query = query(where("name").is("Durzo Blint"));
        Person durzo = action.findOne(query, Person.class);  (2)

        Person azoth = new Person("Kylar Stern");
        azoth.setMaster(durzo);

        action.insert(azoth);                                (3)

        return azoth;
    });

session.close()                                              (4)
  • (1) 从服务器获取新会话。
  • (2) 像以前一样使用MongoOperation方法。 ClientSession自动被应用。
  • (3) 确保关闭ClientSession
  • (4) 关闭会话。

Warning

在处理DBRef个实例(尤其是延迟加载的实例)时,必须在所有数据加载之前“不”关闭ClientSession。否则,延迟获取将失败。

11.2. ReactiveClientSession 支持

反应式对应项使用与命令式相同的构造块,如以下示例所示:

例子 120.带有ReactiveMongoOperations的 ClientSession

ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
    .causallyConsistent(true)
    .build();

Publisher<ClientSession> session = client.startSession(sessionOptions); (1)

template.withSession(session)
    .execute(action -> {

        Query query = query(where("name").is("Durzo Blint"));
        return action.findOne(query, Person.class)
            .flatMap(durzo -> {

                Person azoth = new Person("Kylar Stern");
                azoth.setMaster(durzo);

                return action.insert(azoth);                            (2)
            });
    }, ClientSession::close)                                            (3)
    .subscribe();                                                       (4)
  • (1) 获取Publisher以进行新的会话检索。
  • (2) 像以前一样使用ReactiveMongoOperation方法。 ClientSession已获得并自动应用。
  • (3) 确保关闭ClientSession
  • (4) 除非您订阅,否则什么都不会发生。有关详情,请参见Project Reactor 参考指南

通过使用提供实际会话的Publisher,您可以将会话获取推迟到实际订阅的时间。尽管如此,完成后仍需要关闭会话,以免会话过时污染服务器。当您不再需要会话时,请使用execute上的doFinally钩子来调用ClientSession#close()。如果您希望对会话本身进行更多控制,则可以通过驱动程序获取ClientSession,并通过Supplier提供它。

Note

ClientSession的被动使用仅限于模板 API 的使用。当前没有与 Reactive 存储库的会话集成。

12. MongoDB 事务

从版本 4 开始,MongoDB 支持Transactions。Transaction 构建在Sessions的基础上,因此需要一个有效的ClientSession

Note

除非您在应用程序上下文中指定MongoTransactionManager,否则事务支持为 DISABLED 。您可以使用setSessionSynchronization(ALWAYS)参与正在进行的非本机 MongoDB 事务。

要获得对 Transaction 的完全编程控制,您可能要使用MongoOperations上的会话回调。

以下示例显示了SessionCallback内的程序化事务控制:

例子 121.程序化 Transaction

ClientSession session = client.startSession(options);                   (1)

template.withSession(session)
    .execute(action -> {

        session.startTransaction();                                     (2)

        try {

            Step step = // ...;
            action.insert(step);

            process(step);

            action.update(Step.class).apply(Update.set("state", // ...

            session.commitTransaction();                                (3)

        } catch (RuntimeException e) {
            session.abortTransaction();                                 (4)
        }
    }, ClientSession::close)                                            (5)
  • (1) 获得一个新的ClientSession
  • (2) 开始 Transaction。
  • (3) 如果一切正常,请提交更改。
  • (4) 发生故障,因此请回滚所有内容。
  • (5) 完成后不要忘记关闭会话。

前面的示例使您可以在回调中使用会话范围为MongoOperations的实例时完全控制事务行为,以确保将会话传递给每个服务器调用。为避免此方法带来的一些开销,您可以使用TransactionTemplate消除手动事务处理流程的一些干扰。

12.1. 使用 TransactionTemplate 进行的 Transaction

Spring Data MongoDB 事务支持TransactionTemplate。以下示例显示了如何创建和使用TransactionTemplate

例子 122.与TransactionTemplate的 Transaction

template.setSessionSynchronization(ALWAYS);                                     (1)

// ...

TransactionTemplate txTemplate = new TransactionTemplate(anyTxManager);         (2)

txTemplate.execute(new TransactionCallbackWithoutResult() {

    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {     (3)

        Step step = // ...;
        template.insert(step);

        process(step);

        template.update(Step.class).apply(Update.set("state", // ...
    };
});
  • (1) 在模板 API 配置期间启用事务同步。
  • (2) 使用提供的PlatformTransactionManager创建TransactionTemplate
  • (3) 在回调中,ClientSession和事务已被注册。

Warning

在运行时更改MongoTemplate的状态(如您可能认为在上一清单的项目 1 中可能的那样)会导致线程和可见性问题。

12.2. 与 MongoTransactionManager 进行的 Transaction

MongoTransactionManager是众所周知的 Spring 事务支持的网关。它使应用程序可以使用Spring 的托管 Transaction 功能MongoTransactionManagerClientSession绑定到线程。 MongoTemplate检测到会话并相应地处理与事务相关联的这些资源。 MongoTemplate还可以参与其他正在进行的 Transaction。以下示例显示如何使用MongoTransactionManager创建和使用事务:

例子 123.与MongoTransactionManager的 Transaction

@Configuration
static class Config extends AbstractMongoConfiguration {

    @Bean
    MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {  (1)
        return new MongoTransactionManager(dbFactory);
    }

    // ...
}

@Component
public class StateService {

    @Transactional
    void someBusinessFunction(Step step) {                                  (2)

        template.insert(step);

        process(step);

        template.update(Step.class).apply(Update.set("state", // ...
    };
});
  • (1) 在应用程序上下文中注册MongoTransactionManager
  • (2) 将方法标记为事务性。

Note

@Transactional(readOnly = true)建议MongoTransactionManager也开始将ClientSession添加到传出请求的事务。

12.3. ReactiveTransaction

与响应式ClientSession支持相同,ReactiveMongoTemplate提供了用于在事务内进行操作的专用方法,而不必担心取决于操作结果的提交/中止操作。

Note

除非您在应用程序上下文中指定ReactiveMongoTransactionManager,否则事务支持为 DISABLED 。您可以使用setSessionSynchronization(ALWAYS)参与正在进行的非本机 MongoDB 事务。

使用普通的 MongoDBReactive 驱动程序 API,事务流中的delete可能看起来像这样。

例子 124.本机驱动程序支持

Mono<DeleteResult> result = Mono
    .from(client.startSession())                                                             (1)

    .flatMap(session -> {
        session.startTransaction();                                                          (2)

        return Mono.from(collection.deleteMany(session, ...))                                (3)

            .onErrorResume(e -> Mono.from(session.abortTransaction()).then(Mono.error(e)))   (4)

            .flatMap(val -> Mono.from(session.commitTransaction()).then(Mono.just(val)))     (5)

            .doFinally(signal -> session.close());                                           (6)
      });
  • (1) 首先,我们显然需要启动会话。
  • (2) 一旦有了ClientSession,就开始 Transaction。
  • (3) 通过将ClientSession传递给操作在事务内进行操作。
  • (4) 如果操作异常完成,我们需要中止事务并保留错误。
  • (5) 或者,当然,在成功的情况下提交更改。仍保留操作结果。
  • (6) 最后,我们需要确保关闭会话。

上述操作的罪魁祸首是保持主流DeleteResult而不是通过commitTransaction()abortTransaction()发布的 Transaction 结果,这导致设置相当复杂。

12.4. 与 TransactionalOperator 进行的 Transaction

Spring Data MongoDB 事务支持TransactionalOperator。以下示例显示了如何创建和使用TransactionalOperator

例子 125.与TransactionalOperator的 Transaction

template.setSessionSynchronization(ALWAYS);                                          (1)

// ...

TransactionalOperator rxtx = TransactionalOperator.create(anyTxManager,
                                   new DefaultTransactionDefinition());              (2)

Step step = // ...;
template.insert(step);

Mono<Void> process(step)
    .then(template.update(Step.class).apply(Update.set("state", …))
    .as(rxtx::transactional)                                                         (3)
    .then();
  • (1) 为事务性参与启用事务同步。
  • (2) 使用提供的ReactiveTransactionManager创建TransactionalOperator
  • (3) TransactionalOperator.transactional(…)为所有上游操作提供事务 Management。

12.5. 使用 ReactiveMongoTransactionManager 进行的 Transaction

ReactiveMongoTransactionManager是众所周知的 Spring 事务支持的网关。它允许应用程序利用Spring 的托管 Transaction 功能ReactiveMongoTransactionManagerClientSession绑定到订户ContextReactiveMongoTemplate检测会话并相应地使用与事务关联的这些资源。 ReactiveMongoTemplate还可以参与其他正在进行的 Transaction。以下示例显示了如何使用ReactiveMongoTransactionManager创建和使用事务:

例子 126.与ReactiveMongoTransactionManager的 Transaction

@Configuration
static class Config extends AbstractMongoConfiguration {

    @Bean
    ReactiveMongoTransactionManager transactionManager(ReactiveDatabaseFactory factory) {  (1)
        return new ReactiveMongoTransactionManager(factory);
    }

    // ...
}

@Service
public class StateService {

    @Transactional
    Mono<UpdateResult> someBusinessFunction(Step step) {                                  (2)

        return template.insert(step)
            .then(process(step))
            .then(template.update(Step.class).apply(Update.set("state", …));
    };
});
  • (1) 在应用程序上下文中注册ReactiveMongoTransactionManager
  • (2) 将方法标记为事务性。

Note

@Transactional(readOnly = true)建议ReactiveMongoTransactionManager也开始将ClientSession添加到传出请求的事务。

12.6. Transaction 中的特殊行为

在事务内部,MongoDB 服务器的行为略有不同。

Connection Settings

MongoDB 驱动程序提供专用的副本集名称配置选项,以使驱动程序进入自动检测模式。此选项有助于在事务期间标识副本集主节点和命令路由。

Note

确保将replicaSet添加到 MongoDB URI 中。有关更多详细信息,请参阅连接字符串选项

Collection Operations

MongoDB 不支持事务中的收集操作,例如收集创建。这也会影响首次使用时即时生成的集合。因此,请确保具有所有必需的结构。

Transient Errors

MongoDB 可以为事务执行期间引发的错误添加特殊标签。这些可能表明短暂的故障可能仅通过重试操作而消失。为此,我们强烈建议Spring Retry。但是,可以重写MongoTransactionManager#doCommit(MongoTransactionObject)以实现重试提交操作行为,如 MongoDB 参考手册中所述。

Count

MongoDB count对收集统计信息进行操作,这些统计信息可能无法反映事务中的实际情况。在多文档事务内发出count命令时,服务器以*错误 50851 *响应。 MongoTemplate检测到活动事务后,将使用$match$count运算符将所有公开的count()方法转换并委托给聚合框架,并保留Query设置,例如collation

在聚合计数助手中使用 geo 命令时有限制。不能使用以下运算符,必须将其替换为其他运算符:

  • $where$expr

  • $near$geoWithin$center

  • $nearSphere$geoWithin$centerSphere

使用Criteria.near(…)Criteria.nearSphere(…)的查询必须分别重写为Criteria.within(…) Criteria.withinSphere(…)。存储库查询方法中的near查询关键字也是如此,必须将其更改为within。另请参阅 MongoDB JIRA 票证DRIVERS-518

以下代码段显示了会话绑定闭包内的count用法:

session.startTransaction();

template.withSession(session)
    .execute(action -> {
        action.count(query(where("state").is("active")), Step.class)
        ...

上面的代码片段在以下命令中实现:

db.collection.aggregate(
   [
      { $match: { state: "active" } },
      { $count: "totalEntityCount" }
   ]
)

instead of:

db.collection.find( { state: "active" } ).count()

13.反应式 MongoDB 支持

ReactiveMongoDB 支持包含以下基本功能集:

  • Spring 配置支持使用基于 Java 的@Configuration类,MongoClient实例和副本集。

  • ReactiveMongoTemplate,这是一个辅助类,它通过以被动方式使用MongoOperations来提高生产率。它包括Document实例与 POJO 之间的集成对象 Map。

  • 将异常转换为 Spring 的可移植数据访问异常层次结构。

  • 与 Spring 的ConversionService集成的功能丰富的对象 Map。

  • 基于 Comments 的 Map 元数据,可扩展以支持其他元数据格式。

  • 持久性和 Map 生命周期事件。

  • 基于 Java 的QueryCriteriaUpdate DSL。

  • 自动执行反应式存储库接口,包括对自定义查询方法的支持。

对于大多数任务,应使用ReactiveMongoTemplate或存储库支持,两者均使用丰富的 Map 功能。 ReactiveMongoTemplate是寻找访问功能(例如递增计数器或临时 CRUD 操作)的地方。 ReactiveMongoTemplate还提供了回调方法,以便您可以使用低级 API 构件(例如MongoDatabase)直接与 MongoDB 通信。各种 API 工件上的命名约定的目标是将那些复制到基本的 MongoDB Java 驱动程序中,以便您可以将现有知识 Map 到 Spring API 上。

13.1. 入门

Spring MongoDB 支持需要 MongoDB 2.6 或更高版本以及 Java SE 8 或更高版本。

首先,您需要设置一个正在运行的 MongoDB 服务器。有关如何启动 MongoDB 实例的说明,请参阅MongoDB 快速入门指南。安装后,启动 MongoDB 通常只需执行以下命令即可:${MONGO_HOME}/bin/mongod

要在 STS 中创建 Spring 项目,请转到文件→新建→Spring 模板项目→Simple Spring Utility Project,然后在出现提示时按 Yes。然后 Importing 项目和包名称,例如 org.spring.mongodb.example。

然后将以下内容添加到 pom.xml 依赖项部分。

<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>2.2.1.RELEASE</version>
  </dependency>

  <dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-reactivestreams</artifactId>
    <version>1.12.0</version>
  </dependency>

  <dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>Dysprosium-RELEASE</version>
  </dependency>

</dependencies>

Note

MongoDB 使用两种不同的驱动程序进行阻塞和 Reactive(非阻塞)数据访问。虽然默认情况下提供了阻止操作,但您可以选择加入反应式用法。

首先,通过创建一个简单的Person类来持久化示例,如下所示:

@Document
public class Person {

  private String id;
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  }
}

然后创建一个要运行的应用程序,如下所示:

public class ReactiveMongoApp {

  private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);

  public static void main(String[] args) throws Exception {

    CountDownLatch latch = new CountDownLatch(1);

    ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");

    mongoOps.insert(new Person("Joe", 34))
          .flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
          .doOnNext(person -> log.info(person.toString()))
          .flatMap(person -> mongoOps.dropCollection("person"))
          .doOnComplete(latch::countDown)
          .subscribe();

    latch.await();
  }
}

运行前面的类将产生以下输出:

2016-09-20 14:56:57,373 DEBUG .index.MongoPersistentEntityIndexCreator: 124 - Analyzing class class example.ReactiveMongoApp$Person for index information.
2016-09-20 14:56:57,452 DEBUG .data.mongodb.core.ReactiveMongoTemplate: 975 - Inserting Document containing fields: [_class, name, age] in collection: person
2016-09-20 14:56:57,541 DEBUG .data.mongodb.core.ReactiveMongoTemplate:1503 - findOne using query: { "name" : "Joe"} fields: null for class: class example.ReactiveMongoApp$Person in collection: person
2016-09-20 14:56:57,545 DEBUG .data.mongodb.core.ReactiveMongoTemplate:1979 - findOne using query: { "name" : "Joe"} in db.collection: database.person
2016-09-20 14:56:57,567  INFO                 example.ReactiveMongoApp:  43 - Person [id=57e1321977ac501c68d73104, name=Joe, age=34]
2016-09-20 14:56:57,573 DEBUG .data.mongodb.core.ReactiveMongoTemplate: 528 - Dropped collection [person]

即使在这个简单的示例中,也需要注意一些事项:

  • 您可以使用标准com.mongodb.reactivestreams.client.MongoClient对象和要使用的数据库名称来实例化 Spring Mongo(ReactiveMongoTemplate)的中央帮助程序类。

  • Map 器可用于标准 POJO 对象,而无需任何其他元数据(尽管您可以选择提供该信息.请参见here。)。

  • 约定用于处理 ID 字段,并将其存储在数据库中时将其转换为ObjectId

  • Map 约定可以使用字段访问。请注意,Person类只有吸气剂。

  • 如果构造函数参数名称与存储文档的字段名称匹配,则将它们用于实例化对象

您可以下载一个GitHub 存储库,包含几个示例并进行试用,以了解该库的工作方式。

13.2. 使用 Spring 和 Reactive Streams 驱动程序连接到 MongoDB

使用 MongoDB 和 Spring 时的首要任务之一是使用 IoC 容器创建com.mongodb.reactivestreams.client.MongoClient对象。

13.2.1. 使用基于 Java 的元数据注册 MongoClient 实例

下面的示例演示如何使用基于 Java 的 Bean 元数据注册com.mongodb.reactivestreams.client.MongoClient的实例:

例子 127.使用基于 Java 的 bean 元数据注册 com.mongodb.MongoClient 对象

@Configuration
public class AppConfig {

  /*
   * Use the Reactive Streams Mongo Client API to create a com.mongodb.reactivestreams.client.MongoClient instance.
   */
   public @Bean MongoClient reactiveMongoClient()  {
       return MongoClients.create("mongodb://localhost");
   }
}

这种方法使您可以使用标准的com.mongodb.reactivestreams.client.MongoClient API(您可能已经知道)。

一种替代方法是使用 Spring 的ReactiveMongoClientFactoryBean在容器中注册com.mongodb.reactivestreams.client.MongoClient实例的实例。与直接实例化com.mongodb.reactivestreams.client.MongoClient实例相比,FactoryBean方法还具有为容器提供ExceptionTranslator实现的附加优点,该实现将 MongoDB 异常转换为 Spring 便携式DataAccessException层次结构中针对带有@RepositoryComments 的数据访问类的异常。 Spring 的 DAO 支持功能描述了@Repository的层次结构和用法。

以下示例显示了基于 Java 的 Bean 元数据,该元数据支持@Repository带 Comments 的类的异常转换:

例子 128.使用 Spring 的 MongoClientFactoryBean 注册 com.mongodb.MongoClient 对象并启用 Spring 的异常转换支持

@Configuration
public class AppConfig {

    /*
     * Factory bean that creates the com.mongodb.reactivestreams.client.MongoClient instance
     */
     public @Bean ReactiveMongoClientFactoryBean mongoClient() {

          ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
          clientFactory.setHost("localhost");

          return clientFactory;
     }
}

要访问其他@Configuration或您自己的类中的ReactiveMongoClientFactoryBean创建的com.mongodb.reactivestreams.client.MongoClient对象,请从上下文中获取MongoClient

13.2.2. ReactiveMongoDatabaseFactory 接口

com.mongodb.reactivestreams.client.MongoClient是反应式 MongoDB 驱动程序 API 的入口点,而连接到特定的 MongoDB 数据库实例则需要其他信息,例如数据库名称。有了这些信息,您可以获得com.mongodb.reactivestreams.client.MongoDatabase对象并访问特定 MongoDB 数据库实例的所有功能。 Spring 提供了org.springframework.data.mongodb.core.ReactiveMongoDatabaseFactory接口来引导到数据库的连接。以下清单显示了ReactiveMongoDatabaseFactory接口:

public interface ReactiveMongoDatabaseFactory {

  /**
   * Creates a default {@link MongoDatabase} instance.
   *
   * @return
   * @throws DataAccessException
   */
  MongoDatabase getMongoDatabase() throws DataAccessException;

  /**
   * Creates a {@link MongoDatabase} instance to access the database with the given name.
   *
   * @param dbName must not be {@literal null} or empty.
   * @return
   * @throws DataAccessException
   */
  MongoDatabase getMongoDatabase(String dbName) throws DataAccessException;

  /**
   * Exposes a shared {@link MongoExceptionTranslator}.
   *
   * @return will never be {@literal null}.
   */
  PersistenceExceptionTranslator getExceptionTranslator();
}

org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory类实现ReactiveMongoDatabaseFactory接口,并使用标准com.mongodb.reactivestreams.client.MongoClient实例和数据库名称创建。

您可以在标准 Java 代码中使用它们,而不是使用 IoC 容器创建ReactiveMongoTemplate的实例,如下所示:

public class MongoApp {

  private static final Log log = LogFactory.getLog(MongoApp.class);

  public static void main(String[] args) throws Exception {

    ReactiveMongoOperations mongoOps = new ReactiveMongoOperations(new SimpleReactiveMongoDatabaseFactory(MongoClient.create(), "database"));

    mongoOps.insert(new Person("Joe", 34))
        .flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
        .doOnNext(person -> log.info(person.toString()))
        .flatMap(person -> mongoOps.dropCollection("person"))
        .subscribe();
  }
}

使用SimpleMongoDbFactory入门部分中显示的清单之间的唯一区别。

13.2.3. 使用基于 Java 的元数据注册 ReactiveMongoDatabaseFactory 实例

要在容器中注册ReactiveMongoDatabaseFactory实例,您可以编写类似于上一代码清单中突出显示的代码,如以下示例所示:

@Configuration
public class MongoConfiguration {

  public @Bean ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
    return new SimpleReactiveMongoDatabaseFactory(MongoClients.create(), "database");
  }
}

要定义用户名和密码,请创建一个 MongoDB 连接字符串,并将其传递给 factory 方法,如下清单所示。以下清单还显示了如何使用ReactiveMongoDatabaseFactory向容器注册ReactiveMongoTemplate的实例:

@Configuration
public class MongoConfiguration {

  public @Bean ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
    return new SimpleReactiveMongoDatabaseFactory(MongoClients.create("mongodb://joe:[emailprotected]"), "database");
  }

  public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
    return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory());
  }
}

13.3. ReactiveMongoTemplate 简介

位于org.springframework.data.mongodb包中的ReactiveMongoTemplate类是 Spring 的 Reactive MongoDB 支持的中心类,并提供了丰富的功能集来与数据库进行交互。该模板提供了创建,更新,删除和查询 MongoDB 文档的便捷操作,并提供了域对象和 MongoDB 文档之间的 Map。

Note

配置完成后,ReactiveMongoTemplate是线程安全的,并且可以在多个实例之间重用。

MongoDB 文档和域类之间的 Map 是通过委派MongoConverter接口的实现来完成的。 Spring 提供了MongoMappingConverter的默认实现,但是您也可以编写自己的转换器。有关更多详细信息,请参见关于 MongoConverter 实例的部分

ReactiveMongoTemplate类实现ReactiveMongoOperations接口。 ReactiveMongoOperations上的方法尽可能地镜像 MongoDB 驱动程序Collection对象上可用的方法,以使熟悉该驱动程序 API 的现有 MongoDB 开发人员熟悉该 API。例如,您可以找到findfindAndModifyfindOneinsertremovesaveupdateupdateMulti之类的方法。设计目标是使在基本 MongoDB 驱动程序和ReactiveMongoOperations的使用之间的转换尽可能容易。两种 API 之间的主要区别是ReactiveMongoOperations可以传递域对象而不是Document,并且 Fluent 的 api 用于QueryCriteriaUpdate操作,而不是填充Document来指定这些操作的参数。

Note

引用ReactiveMongoTemplate实例上的操作的首选方法是通过其ReactiveMongoOperations接口。

ReactiveMongoTemplate使用的默认转换器实现是MappingMongoConverterMappingMongoConverter可以使用其他元数据来指定对象到文档的 Map,但是它也可以通过使用一些 ID 和集合名称的 Map 约定来转换不包含其他元数据的对象。这些约定以及 MapComments 的用法在Mapping chapter中进行了说明。

ReactiveMongoTemplate的另一个主要功能是将 MongoDB Java 驱动程序中引发的异常转换为 Spring 的可移植数据访问异常层次结构。有关更多信息,请参见exception translation部分。

ReactiveMongoTemplate上有许多便捷方法可以帮助您轻松执行常见任务。但是,如果您需要直接访问 MongoDB 驱动程序 API 来访问未由 MongoTemplate 显式公开的功能,则可以使用几种execute回调方法之一来访问基础驱动程序 API。 execute回调为您提供对com.mongodb.reactivestreams.client.MongoCollectioncom.mongodb.reactivestreams.client.MongoDatabase对象的引用。有关更多信息,请参见Execution Callbacks

13.3.1. 实例化 ReactiveMongoTemplate

您可以使用 Java 创建和注册ReactiveMongoTemplate的实例,如下所示:

例子 129.注册一个com.mongodb.reactivestreams.client.MongoClient对象并启用 Spring 的异常转换支持

@Configuration
public class AppConfig {

  public @Bean MongoClient reactiveMongoClient() {
      return MongoClients.create("mongodb://localhost");
  }

  public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
      return new ReactiveMongoTemplate(reactiveMongoClient(), "mydatabase");
  }
}

ReactiveMongoTemplate有多个重载的构造函数,包括:

  • ReactiveMongoTemplate(MongoClient mongo, String databaseName):使用com.mongodb.MongoClient对象和默认数据库名称进行操作。

  • ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory):采用封装了com.mongodb.reactivestreams.client.MongoClient对象和数据库名称的ReactiveMongoDatabaseFactory对象。

  • ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter):添加一个MongoConverter用于 Map。

创建ReactiveMongoTemplate时,您可能还需要设置以下属性:

  • WriteResultCheckingPolicy

  • WriteConcern

  • ReadPreference

Note

引用ReactiveMongoTemplate实例上的操作的首选方法是通过其ReactiveMongoOperations接口。

13.3.2. WriteResultChecking 策略

在开发中,如果从任何 MongoDB 操作返回的com.mongodb.WriteResult包含错误,则可以方便地记录或抛出Exception。通常很容易忘记在开发过程中执行此操作,然后最终得到一个看起来运行成功的应用程序,而实际上该数据库并未根据您的期望进行修改。将MongoTemplate WriteResultChecking属性设置为具有以下值LOGEXCEPTIONNONE的枚举,以记录错误,引发异常和不执行任何操作。默认值为NONEWriteResultChecking值。

13.3.3. WriteConcern

如果尚未通过更高级别的驱动程序(例如MongoDatabase)指定它,则可以设置ReactiveMongoTemplate用于写操作的com.mongodb.WriteConcern属性。如果未设置 ReactiveMongoTemplate 的WriteConcern属性,则默认为 MongoDB 驱动程序的MongoDatabaseMongoCollection设置中的一个。

13.3.4. WriteConcernResolver

对于更高级的情况,您希望基于每个操作设置不同的WriteConcern值(用于删除,更新,插入和保存操作),可以在ReactiveMongoTemplate上配置名为WriteConcernResolver的策略接口。由于ReactiveMongoTemplate用于持久化 POJO,因此WriteConcernResolver使您可以创建可将特定 POJO 类 Map 到WriteConcern值的策略。以下清单显示了WriteConcernResolver接口:

public interface WriteConcernResolver {
  WriteConcern resolve(MongoAction action);
}

参数MongoAction确定要使用的WriteConcern值,以及是否使用模板本身的值作为默认值。 MongoAction包含要写入的集合名称,POJO 的java.lang.Class,转换的DBObject,作为MongoActionOperation枚举(REMOVEUPDATEINSERTINSERT_LISTSAVE之一)中的值的操作。信息。以下示例显示了如何创建WriteConcernResolver

private class MyAppWriteConcernResolver implements WriteConcernResolver {

  public WriteConcern resolve(MongoAction action) {
    if (action.getEntityClass().getSimpleName().contains("Audit")) {
      return WriteConcern.NONE;
    } else if (action.getEntityClass().getSimpleName().contains("Metadata")) {
      return WriteConcern.JOURNAL_SAFE;
    }
    return action.getDefaultWriteConcern();
  }
}

13.4. 保存,更新和删除文档

ReactiveMongoTemplate使您可以保存,更新和删除域对象,并将这些对象 Map 到 MongoDB 中存储的文档。

考虑以下Person类:

public class Person {

  private String id;
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
  }

}

以下清单显示了如何保存,更新和删除Person对象:

public class ReactiveMongoApp {

  private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);

  public static void main(String[] args) throws Exception {

    CountDownLatch latch = new CountDownLatch(1);

    ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");

    mongoOps.insert(new Person("Joe", 34)).doOnNext(person -> log.info("Insert: " + person))
      .flatMap(person -> mongoOps.findById(person.getId(), Person.class))
      .doOnNext(person -> log.info("Found: " + person))
      .zipWith(person -> mongoOps.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class))
      .flatMap(tuple -> mongoOps.remove(tuple.getT1())).flatMap(deleteResult -> mongoOps.findAll(Person.class))
      .count().doOnSuccess(count -> {
        log.info("Number of people: " + count);
        latch.countDown();
      })

      .subscribe();

    latch.await();
  }
}

前面的示例包括存储在数据库中的StringObjectId之间的隐式转换(通过使用MongoConverter),并识别属性Id名称的约定。

Note

前面的示例旨在显示对ReactiveMongoTemplate的保存,更新和删除操作的使用,而不显示复杂的 Map 或链接功能。

Querying Documents”更详细地说明了前面示例中使用的查询语法。其他文档可在阻塞的 MongoTemplate部分中找到。

13.5. 执行回调

所有 Spring 模板类的共同设计 Feature 是所有功能都路由到其中一个模板执行回调方法中。这有助于确保异常和可能需要的所有资源 Management 的执行一致性。尽管在 JDBC 和 JMS 中这比在 MongoDB 中有更大的需要,但它仍然为异常转换和日志记录提供了一个唯一的起点。因此,使用 execute 回调是访问 MongoDB 驱动程序的MongoDatabaseMongoCollection对象以执行未在ReactiveMongoTemplate上作为方法公开的不常见操作的首选方法。

这是执行回调方法的列表。

  • <T> Flux<T> execute (Class<?> entityClass, ReactiveCollectionCallback<T> action):对指定类的实体集合执行给定的ReactiveCollectionCallback

  • <T> Flux<T> execute (String collectionName, ReactiveCollectionCallback<T> action):对给定名称的集合执行给定ReactiveCollectionCallback

  • <T> Flux<T> execute (ReactiveDatabaseCallback<T> action):执行ReactiveDatabaseCallback,翻译任何必要的异常。

下面的示例使用ReactiveCollectionCallback返回有关索引的信息:

Flux<Boolean> hasIndex = operations.execute("geolocation",
    collection -> Flux.from(collection.listIndexes(Document.class))
      .filter(document -> document.get("name").equals("fancy-index-name"))
      .flatMap(document -> Mono.just(true))
      .defaultIfEmpty(false));

13.6. GridFS 支持

MongoDB 支持在其文件系统 GridFS 中存储二进制文件。 Spring Data MongoDB 提供了一个ReactiveGridFsOperations接口以及相应的实现ReactiveGridFsTemplate,以使您与文件系统进行交互。您可以将ReactiveMongoDatabaseFactoryMongoConverter递给ReactiveGridFsTemplate实例,如下例所示:

例子 130.为 ReactiveGridFsTemplate 设置 JavaConfig

class GridFsConfiguration extends AbstractReactiveMongoConfiguration {

  // … further configuration omitted

  @Bean
  public ReactiveGridFsTemplate reactiveGridFsTemplate() {
    return new ReactiveGridFsTemplate(reactiveMongoDbFactory(), mappingMongoConverter());
  }
}

现在可以将模板注入并用于执行存储和检索操作,如以下示例所示:

例子 131.使用 ReactiveGridFsTemplate 来存储文件

class ReactiveGridFsClient {

  @Autowired
  ReactiveGridFsTemplate operations;

  @Test
  public Mono<ObjectId> storeFileToGridFs() {

    FileMetadata metadata = new FileMetadata();
    // populate metadata
    Publisher<DataBuffer> file = … // lookup File or Resource

    return operations.store(file, "filename.txt", metadata);
  }
}

store(…)操作采用Publisher<DataBuffer>,文件名和(可选)有关要存储的文件的元数据信息。元数据可以是任意对象,由配置为ReactiveGridFsTemplateMongoConverter编组。或者,您也可以提供Document

Note

MongoDB 的驱动程序使用AsyncInputStreamAsyncOutputStream接口交换二进制流。 Spring Data MongoDB 使这些接口适应Publisher<DataBuffer>。在Spring 的参考文档中了解有关DataBuffer的更多信息。

您可以通过find(…)getResources(…)方法从文件系统读取文件。首先让我们看一下find(…)方法。您可以找到一个或多个与Query匹配的文件。您可以使用GridFsCriteria helper 类来定义查询。它提供了静态工厂方法来封装默认的元数据字段(例如whereFilename()whereContentType())或自定义字段到whereMetaData()。下面的示例显示如何使用ReactiveGridFsTemplate查询文件:

例子 132.使用 ReactiveGridFsTemplate 查询文件

class ReactiveGridFsClient {

  @Autowired
  ReactiveGridFsTemplate operations;

  @Test
  public Flux<GridFSFile> findFilesInGridFs() {
    return operations.find(query(whereFilename().is("filename.txt")))
  }
}

Note

当前,从 GridFS 检索文件时,MongoDB 不支持定义排序条件。出于这个原因,将忽略在传递给find(…)方法的Query实例上定义的任何排序条件。

从 GridF 读取文件的另一种方法是使用沿着ResourcePatternResolver建模的方法。 ReactiveGridFsOperations使用响应类型来推迟执行,而ResourcePatternResolver使用同步接口。这些方法允许将 Ant 路径传递给该方法,因此可以检索与给定模式匹配的文件。以下示例显示如何使用ReactiveGridFsTemplate读取文件:

例子 133.使用 ReactiveGridFsTemplate 读取文件

class ReactiveGridFsClient {

  @Autowired
  ReactiveGridFsOperations operations;

  @Test
  public void readFilesFromGridFs() {
     Flux<ReactiveGridFsResource> txtFiles = operations.getResources("*.txt");
  }
}

14. MongoDB 存储库

14.1. Introduction

本章指出了 MongoDB 存储库支持的特殊性。本章以使用 Spring 数据存储库中解释的核心存储库支持为基础。您应该对这里介绍的基本概念有一个很好的了解。

14.2. Usage

要访问存储在 MongoDB 中的域实体,您可以使用我们的完善的存储库支持,从而大大简化了实现。为此,请为您的存储库创建一个接口,如以下示例所示:

例子 134.采样人实体

public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

请注意,上例中显示的域类型具有名为id的属性,类型为ObjectIdMongoTemplate(支持存储库支持)中使用的默认序列化机制将名为id的属性视为文档 ID。目前,我们支持StringObjectIdBigInteger作为 ID 类型。现在我们有了一个域对象,我们可以定义一个使用它的接口,如下所示:

例子 135.持久化 Person 实体的基本存储库接口

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {

  // additional custom query methods go here
}

现在,此接口仅用于提供类型信息,但是我们稍后可以向其添加其他方法。为此,在您的 Spring 配置中,添加以下内容:

例子 136.一般的 MongoDB 存储库 Spring 配置

<?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/beans
    https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/data/mongo
    https://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">

  <mongo:mongo-client id="mongoClient" />

  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongoClient" />
    <constructor-arg value="databaseName" />
  </bean>

  <mongo:repositories base-package="com.acme.*.repositories" />

</beans>

这个名称空间元素使基本软件包被扫描以寻找扩展MongoRepository的接口,并为找到的每个 bean 创建 Spring bean。默认情况下,存储库会连接一个名为mongoTemplateMongoTemplate Spring bean 连线,因此,如果您偏离此约定,则仅需要显式配置mongo-template-ref

如果您希望使用基于 Java 的配置,请使用@EnableMongoRepositoriesComments。该 Comments 具有与名称空间元素相同的属性。如果未配置基本软件包,则基础结构将扫描带 Comments 的配置类的软件包。以下示例显示如何对存储库使用 Java 配置:

例子 137.仓库的 Java 配置

@Configuration
@EnableMongoRepositories
class ApplicationConfig extends AbstractMongoConfiguration {

  @Override
  protected String getDatabaseName() {
    return "e-store";
  }

  @Override
  public MongoClient mongoClient() {
    return new MongoClient();
  }

  @Override
  protected String getMappingBasePackage() {
    return "com.oreilly.springdata.mongodb"
  }
}

因为我们的域存储库扩展了PagingAndSortingRepository,所以它为您提供 CRUD 操作以及对实体进行分页和排序访问的方法。使用存储库实例只是将其注入 Client 端的依赖关系。因此,以 10 页的大小访问Person对象的第二页将类似于以下代码:

例子 138.分页访问 Person 实体

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PersonRepositoryTests {

    @Autowired PersonRepository repository;

    @Test
    public void readsFirstPageCorrectly() {

      Page<Person> persons = repository.findAll(PageRequest.of(0, 10));
      assertThat(persons.isFirstPage(), is(true));
    }
}

前面的示例在 Spring 的单元测试支持下创建了一个应用程序上下文,该上下文将基于 Comments 的依赖项注入到测试用例中。在测试方法内部,我们使用存储库查询数据存储。我们向存储库提供一个PageRequest实例,该实例以页面大小 10 请求Person对象的第一页。

14.3. 查询方法

您通常在存储库上触发的大多数数据访问操作都会导致对 MongoDB 数据库执行查询。定义此类查询只需在存储库接口上声明一个方法即可,如以下示例所示:

例子 139.使用查询方法的 PersonRepository

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {

    List<Person> findByLastname(String lastname);                      (1)

    Page<Person> findByFirstname(String firstname, Pageable pageable); (2)

    Person findByShippingAddresses(Address address);                   (3)

    Person findFirstByLastname(String lastname)                        (4)

    Stream<Person> findAllBy();                                        (5)
}
  • (1) findByLastname方法显示一个查询所有具有给定姓氏的人。通过解析方法名称以获取可以与AndOr串联的约束来导出查询。因此,方法名称导致查询表达式{"lastname" : lastname}
  • (2) 将分页应用于查询。您可以为方法签名配备Pageable参数,并让该方法返回Page实例,然后 Spring Data 会相应地自动分页查询。
  • (3) 显示您可以基于非原始类型的属性进行查询。如果找到多个匹配项,则抛出IncorrectResultSizeDataAccessException
  • (4) 使用First关键字将查询限制为仅第一个结果。与\ <3>不同,如果找到多个匹配项,则此方法不会引发异常。
  • (5) 使用 Java 8 Stream在迭代流时读取和转换单个元素。

Note

我们不支持引用在域类中 Map 为DBRef的参数。

下表显示了查询方法支持的关键字:

表 10.查询方法支持的关键字

Keyword Sample Logical result
After findByBirthdateAfter(Date date) {"birthdate" : {"$gt" : date}}
GreaterThan findByAgeGreaterThan(int age) {"age" : {"$gt" : age}}
GreaterThanEqual findByAgeGreaterThanEqual(int age) {"age" : {"$gte" : age}}
Before findByBirthdateBefore(Date date) {"birthdate" : {"$lt" : date}}
LessThan findByAgeLessThan(int age) {"age" : {"$lt" : age}}
LessThanEqual findByAgeLessThanEqual(int age) {"age" : {"$lte" : age}}
Between findByAgeBetween(int from, int to)

findByAgeBetween(Range<Integer> range)
{"age" : {"$gt" : from, "$lt" : to}}
根据Range的下限/上限($gt/$gte$lt/$lte)
In findByAgeIn(Collection ages) {"age" : {"$in" : [ages…]}}
NotIn findByAgeNotIn(Collection ages) {"age" : {"$nin" : [ages…]}}
IsNotNullNotNull findByFirstnameNotNull() {"firstname" : {"$ne" : null}}
IsNullNull findByFirstnameNull() {"firstname" : null}
LikeStartingWithEndingWith findByFirstnameLike(String name) {"firstname" : name} (name as regex)
NotLikeIsNotLike findByFirstnameNotLike(String name) {"firstname" : { "$not" : name }} (name as regex)
Containing在字符串 findByFirstnameContaining(String name) {"firstname" : name} (name as regex)
NotContaining在字符串 findByFirstnameNotContaining(String name) {"firstname" : { "$not" : name}} (name as regex)
Containing关于收藏 findByAddressesContaining(Address address) {"addresses" : { "$in" : address}}
NotContaining关于收藏 findByAddressesNotContaining(Address address) {"addresses" : { "$not" : { "$in" : address}}}
Regex findByFirstnameRegex(String firstname) {"firstname" : {"$regex" : firstname }}
(No keyword) findByFirstname(String name) {"firstname" : name}
Not findByFirstnameNot(String name) {"firstname" : {"$ne" : name}}
Near findByLocationNear(Point point) {"location" : {"$near" : [x,y]}}
Near findByLocationNear(Point point, Distance max) {"location" : {"$near" : [x,y], "$maxDistance" : max}}
Near findByLocationNear(Point point, Distance min, Distance max) {"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}
Within findByLocationWithin(Circle circle) {"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}
Within findByLocationWithin(Box box) {"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}
IsTrueTrue findByActiveIsTrue() {"active" : true}
IsFalseFalse findByActiveIsFalse() {"active" : false}
Exists findByLocationExists(boolean exists) {"location" : {"$exists" : exists }}

Note

如果属性标准比较文档,则字段 Sequences 和文档中的完全相等很重要。

14.3.1. Repositories 删除查询

上表中的关键字可以与delete…Byremove…By结合使用,以创建删除匹配文档的查询。

例子 140. Delete…By查询

public interface PersonRepository extends MongoRepository<Person, String> {

  List <Person> deleteByLastname(String lastname);

  Long deletePersonByLastname(String lastname);
}

使用返回类型List检索并返回所有匹配的文档,然后再将其删除。数字返回类型直接删除匹配的文档,并返回删除的文档总数。

14.3.2. 地理空间存储库查询

如上表中的关键字所示,一些关键字触发了 MongoDB 查询中的地理空间操作。 Near关键字允许进行进一步的修改,如以下几个示例所示。

以下示例显示如何定义一个near查询,该查询查找具有给定点给定距离的所有人员:

例子 141.高级Near查询

public interface PersonRepository extends MongoRepository<Person, String>

  // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
  List<Person> findByLocationNear(Point location, Distance distance);
}

在查询方法中添加Distance参数可以将结果限制在给定距离内。如果设置的Distance包含Metric,我们将透明地使用$nearSphere而不是$code,如以下示例所示:

例子 142.将DistanceMetrics一起使用

Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}

DistanceMetric一起使用会导致添加$nearSphere(而不是普通的$near)子句。除此之外,实际距离将根据使用的Metrics计算得出。

(请注意,Metric不是指公制度量单位.它可能是英里而不是公里.相反,metric指的是度量系统的概念,无论使用哪种系统.)

Note

在目标属性上使用@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)会强制使用$nearSphere运算符。

Geo-near Queries

Spring Data MongoDb 支持近地查询,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>

  // {'geoNear' : 'location', 'near' : [x, y] }
  GeoResults<Person> findByLocationNear(Point location);

  // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
  // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
  //          'distanceMultiplier' : metric.multiplier, 'spherical' : true }
  GeoResults<Person> findByLocationNear(Point location, Distance distance);

  // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
  //          'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
  //          'spherical' : true }
  GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);

  // {'geoNear' : 'location', 'near' : [x, y] }
  GeoResults<Person> findByLocationNear(Point location);
}

14.3.3. 基于 MongoDB JSON 的查询方法和字段限制

通过向存储库查询方法添加org.springframework.data.mongodb.repository.Query注解,您可以指定要使用的 MongoDB JSON 查询字符串,而不是从方法名派生该查询,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>

  @Query("{ 'firstname' : ?0 }")
  List<Person> findByThePersonsFirstname(String firstname);

}

?0占位符使您可以将方法参数中的值替换为 JSON 查询字符串。

Note

String参数值在绑定过程中转义,这意味着不可能通过参数添加 MongoDB 特定的运算符。

您还可以使用 filter 属性来限制 Map 到 Java 对象的属性集,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>

  @Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
  List<Person> findByThePersonsFirstname(String firstname);

}

前面示例中的查询仅返回Person对象的firstnamelastnameId属性。未设置age属性java.lang.Integer,因此其值为 null。

14.3.4. 排序查询方法结果

MongoDB 存储库允许使用各种方法来定义排序 Sequences。让我们看下面的例子:

例子 143.对查询结果排序

public interface PersonRepository extends MongoRepository<Person, String> {

  List<Person> findByFirstnameSortByAgeDesc(String firstname); (1)

  List<Person> findByFirstname(String firstname, Sort sort);   (2)

  @Query(sort = "{ age : -1 }")
  List<Person> findByFirstname(String firstname);              (3)

  @Query(sort = "{ age : -1 }")
  List<Person> findByLastname(String lastname, Sort sort);     (4)
}
  • (1) 从方法名称派生的静态排序。 SortByAgeDesc产生{ age : -1 }作为排序参数。
  • (2) 使用方法参数进行动态排序。 Sort.by(DESC, "age")为 sort 参数创建{ age : -1 }
  • (3) 通过QueryComments 进行静态排序。如sort属性中所述应用排序参数。
  • (4) 通过QueryComments 的默认排序与通过方法参数的动态 Comments 结合在一起。 Sort.unsorted()产生{ age : -1 }。使用Sort.by(ASC, "age")会覆盖默认值并创建{ age : 1 }Sort.by (ASC, "firstname")更改默认值并产生{ age : -1, firstname : 1 }

14.3.5. 具有 SpEL 表达式的基于 JSON 的查询

查询字符串和字段定义可以与 SpEL 表达式一起使用,以在运行时创建动态查询。 SpEL 表达式可以提供谓词值,并且可以用于扩展带有子文档的谓词。

表达式通过包含所有参数的数组公开方法参数。以下查询使用[0]声明lastname的谓词值(等效于?0参数绑定):

public interface PersonRepository extends MongoRepository<Person, String>

  @Query("{'lastname': ?#{[0]} }")
  List<Person> findByQueryWithExpression(String param0);
}

表达式可用于调用函数,评估条件和构造值。与 JSON 结合使用的 SpEL 表达式具有副作用,因为 SpEL 内的类似 Map 的声明类似于 JSON,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>

  @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
  List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}

查询字符串中的 SpEL 可能是增强查询的强大方法。但是,它们也可以接受各种不需要的参数。您应确保在将字符串传递给查询之前对字符串进行清理,以免对查询进行不必要的更改。

表达式支持可通过查询 SPI org.springframework.data.repository.query.spi.EvaluationContextExtension扩展。查询 SPI 可以提供属性和功能,并且可以自定义根对象。在构建查询时,在 SpEL 评估时从应用程序上下文检索扩展。以下示例显示了如何使用EvaluationContextExtension

public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {

  @Override
  public String getExtensionId() {
    return "security";
  }

  @Override
  public Map<String, Object> getProperties() {
    return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
  }
}

Note

引导MongoRepositoryFactory本身并不了解应用程序上下文,因此需要进一步配置以获取 Query SPI 扩展。

14.3.6. 类型安全的查询方法

MongoDB 存储库支持与Querydsl项目集成在一起,该项目提供了一种执行类型安全查询的方式。从项目描述中引用:“不是将查询编写为内联字符串或将其外部化为 XML 文件,而是通过流畅的 API 来构造它们。”它提供以下功能:

  • IDE 中的代码完成(可以在您喜欢的 Java IDE 中扩展所有属性,方法和操作)。

  • 几乎不允许语法上无效的查询(在所有级别上都是类型安全的)。

  • 可以安全地引用域类型和属性-不涉及任何字符串!

  • 更好地适应重构域类型的更改。

  • 增量查询定义更容易。

有关如何使用 Maven 或 Ant 引导环境以生成基于 APT 的代码的信息,请参见QueryDSL documentation

QueryDSL 使您可以编写如下查询:

QPerson person = new QPerson("person");
List<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

Page<Person> page = repository.findAll(person.lastname.contains("a"),
                                       PageRequest.of(0, 2, Direction.ASC, "lastname"));

QPerson是由 Java 注解后处理工具生成的类。它是Predicate,可让您编写类型安全的查询。请注意,查询中除C0123值外没有其他字符串。

您可以通过QuerydslPredicateExecutor接口使用生成的Predicate类,以下清单显示了该接口:

public interface QuerydslPredicateExecutor<T> {

  T findOne(Predicate predicate);

  List<T> findAll(Predicate predicate);

  List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

  Page<T> findAll(Predicate predicate, Pageable pageable);

  Long count(Predicate predicate);
}

要在存储库实现中使用它,请将其添加到继承接口的存储库接口列表中,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>, QuerydslPredicateExecutor<Person> {

   // additional query methods go here
}

14.3.7. 全文搜索查询

MongoDB 的全文本搜索功能是特定于 Store 的,因此可以在MongoRepository而不是更通用的CrudRepository上找到。我们需要一个带有全文索引的文档(请参阅“ Text Indexes”以了解如何创建全文索引)。

MongoRepository上的其他方法将TextCriteria作为 Importing 参数。除了这些显式方法之外,还可以添加TextCriteria派生的存储库方法。该标准将作为附加的AND标准添加。一旦实体包含带有@TextScoreComments 的属性,就可以检索文档的全文评分。此外,带 Comments 的@TextScore还可以按文档的分数进行排序,如以下示例所示:

@Document
class FullTextDocument {

  @Id String id;
  @TextIndexed String title;
  @TextIndexed String content;
  @TextScore Float score;
}

interface FullTextRepository extends Repository<FullTextDocument, String> {

  // Execute a full-text search and define sorting dynamically
  List<FullTextDocument> findAllBy(TextCriteria criteria, Sort sort);

  // Paginate over a full-text search result
  Page<FullTextDocument> findAllBy(TextCriteria criteria, Pageable pageable);

  // Combine a derived query with a full-text search
  List<FullTextDocument> findByTitleOrderByScoreDesc(String title, TextCriteria criteria);
}

Sort sort = Sort.by("score");
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("spring", "data");
List<FullTextDocument> result = repository.findAllBy(criteria, sort);

criteria = TextCriteria.forDefaultLanguage().matching("film");
Page<FullTextDocument> page = repository.findAllBy(criteria, PageRequest.of(1, 1, sort));
List<FullTextDocument> result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);

14.3.8. Projections

Spring Data 查询方法通常返回存储库 Management 的聚合根的一个或多个实例。但是,有时可能需要根据这些类型的某些属性创建投影。 Spring Data 允许对专用的返回类型进行建模,以更选择性地检索托管聚合的部分视图。

想象一下一个存储库和聚合根类型,例如以下示例:

例子 144.一个 samples 集合和存储库

class Person {

  @Id UUID id;
  String firstname, lastname;
  Address address;

  static class Address {
    String zipCode, city, street;
  }
}

interface PersonRepository extends Repository<Person, UUID> {

  Collection<Person> findByLastname(String lastname);
}

现在假设我们只想检索此人的姓名属性。 Spring Data 提供什么手段来实现这一目标?本章其余部分将回答该问题。

Interface-based Projections

将查询结果限制为仅名称属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:

例子 145.一个投影接口来检索属性的子集

interface NamesOnly {

  String getFirstname();
  String getLastname();
}

这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。这样做可以使查询方法添加如下:

例子 146.使用基于接口的投影和查询方法的存储库

interface PersonRepository extends Repository<Person, UUID> {

  Collection<NamesOnly> findByLastname(String lastname);
}

查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对公开方法的调用转发给目标对象。

投影可以递归使用。如果您还想包括一些Address信息,请为此创建一个投影接口,并从getAddress()的声明中返回该接口,如以下示例所示:

例子 147.一个投影界面来检索属性的子集

interface PersonSummary {

  String getFirstname();
  String getLastname();
  AddressSummary getAddress();

  interface AddressSummary {
    String getCity();
  }
}

在方法调用时,将获得目标实例的address属性,并将其包装到投影代理中。

Closed Projections

其访问者方法均与目标集合的属性完全匹配的投影接口被视为封闭投影。下面的示例(也在本章前面使用过)是一个封闭的投影:

例子 148.一个封闭的投影

interface NamesOnly {

  String getFirstname();
  String getLastname();
}

如果您使用封闭式投影,Spring Data 可以优化查询执行,因为我们知道支持投影代理所需的所有属性。有关更多信息,请参见参考文档中特定于模块的部分。

Open Projections

投影接口中的访问器方法也可以通过使用@ValueComments 来计算新值,如以下示例所示:

例子 149.一个开放的投影

interface NamesOnly {

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
  …
}

target变量中提供了支持投影的聚合根。使用@Value的投影界面是开放式投影。在这种情况下,Spring Data 无法应用查询执行优化,因为 SpEL 表达式可以使用聚合根的任何属性。

@Value中使用的表达式应该不太复杂-您要避免在String变量中进行编程。对于非常简单的表达式,一种选择可能是求助于默认方法(在 Java 8 中引入),如以下示例所示:

例子 150.使用默认方法自定义逻辑的投影接口

interface NamesOnly {

  String getFirstname();
  String getLastname();

  default String getFullName() {
    return getFirstname.concat(" ").concat(getLastname());
  }
}

这种方法要求您能够完全基于投影接口上公开的其他访问器方法来实现逻辑。第二个更灵活的选择是在 Spring bean 中实现自定义逻辑,然后从 SpEL 表达式中调用该自定义逻辑,如以下示例所示:

例子 151.采样人对象

@Component
class MyBean {

  String getFullName(Person person) {
    …
  }
}

interface NamesOnly {

  @Value("#{@myBean.getFullName(target)}")
  String getFullName();
  …
}

请注意 SpEL 表达式如何引用myBean并调用getFullName(…)方法并将转发目标作为方法参数转发。 SpEL 表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数。方法参数可通过名为argsObject数组获得。下面的示例演示如何从args数组获取方法参数:

例子 152.采样人对象

interface NamesOnly {

  @Value("#{args[0] + ' ' + target.firstname + '!'}")
  String getSalutation(String prefix);
}

同样,对于更复杂的表达式,应使用 Spring bean 并让表达式调用方法,如earlier所述。

基于类的投影(DTO)

定义投影的另一种方法是使用值类型 DTO(数据传输对象),这些 DTO 拥有应该被检索的字段的属性。这些 DTO 类型可以以与使用投影接口完全相同的方式使用,除了不会发生代理和无法应用嵌套的投影。

如果 Store 通过限制要加载的字段来优化查询执行,则要加载的字段由公开的构造函数的参数名称确定。

以下示例显示了一个预计的 DTO:

例子 153.一个投影的 DTO

class NamesOnly {

  private final String firstname, lastname;

  NamesOnly(String firstname, String lastname) {

    this.firstname = firstname;
    this.lastname = lastname;
  }

  String getFirstname() {
    return this.firstname;
  }

  String getLastname() {
    return this.lastname;
  }

  // equals(…) and hashCode() implementations
}

Avoid boilerplate code for projection DTOs

您可以使用Project Lombok大大简化 DTO 的代码,该Project Lombok提供@ValueComments(不要与前面的界面示例中显示的 Spring 的@ValueComments 混淆)。如果您使用 Project Lombok 的@Value注解,则前面显示的示例 DTO 将变为以下内容:

@Value
class NamesOnly {
	String firstname, lastname;
}

默认情况下,字段是private final,并且该类公开了一个构造函数,该构造函数接受所有字段并自动获取实现的equals(…)hashCode()方法。

Dynamic Projections

到目前为止,我们已经将投影类型用作集合的返回类型或元素类型。但是,您可能希望选择调用时要使用的类型(这使它成为动态的)。要应用动态投影,请使用查询方法,如以下示例中所示:

例子 154.一个使用动态投影参数的仓库

interface PersonRepository extends Repository<Person, UUID> {

  <T> Collection<T> findByLastname(String lastname, Class<T> type);
}

这样,该方法可用于按原样或应用投影来获取聚合,如以下示例所示:

例子 155.使用一个带有动态投影的仓库

void someMethod(PersonRepository people) {

  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}

14.3.9. 聚集存储库方法

存储库层提供了通过带 Comments 的存储库查询方法与聚合框架进行交互的方式。与基于 JSON 的查询类似,您可以使用org.springframework.data.mongodb.repository.Aggregation注解定义管道。该定义可以包含简单的占位符,如?0以及SpEL expressions ?#{ … }

例子 156.聚合存储库方法

public interface PersonRepository extends CrudReppsitory<Person, String> {

  @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
  List<PersonAggregate> groupByLastnameAndFirstnames();                            (1)

  @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
  List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort);                   (2)

  @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }")
  List<PersonAggregate> groupByLastnameAnd(String property);                       (3)

  @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }")
  List<PersonAggregate> groupByLastnameAnd(String property, Pageable page);        (4)

  @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
  SumValue sumAgeUsingValueWrapper();                                              (5)

  @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
  Long sumAge();                                                                   (6)

  @Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
  AggregationResults<SumValue> sumAgeRaw();                                        (7)

  @Aggregation("{ '$project': { '_id' : '$lastname' } }")
  List<String> findAllLastnames();                                                 (8)
}
public class PersonAggregate {

  private @Id String lastname;                                                     (2)
  private List<String> names;

  public PersonAggregate(String lastname, List<String> names) {
     // ...
  }

  // Getter / Setter omitted
}

public class SumValue {

  private final Long total;                                                        (5) (7)

  public SumValue(Long total) {
    // ...
  }

  // Getter omitted
}
  • (1) 聚合管道,用于根据Person集合中的lastname对名字进行分组,并将其返回为PersonAggregate
  • (2) 如果存在Sort参数,则在声明的管线阶段之后附加$sort,以便仅在经过所有其他聚合阶段后才影响最终结果的 Sequences。因此,Sort属性是针对方法返回类型PersonAggregateMap 的,该方法将Sort.by("lastname")转换为{ $sort : { '_id', 1 } },因为PersonAggregate.lastname@IdComments。
  • (3)property的给定值替换?0以用于动态聚合管道。
  • (4) $skip$limit$sort可以通过Pageable参数传递。与\ <2>中相同,运算符被附加到管道定义中。
  • (5) 将返回单个Document的聚合结果 Map 到所需SumValue目标类型的实例。
  • (6) 导致单个文档仅包含诸如$sum可以直接从结果Document中提取。为了获得更多控制权,您可以将AggregationResult作为方法返回类型,如\ <7>所示。
  • (7) 获取 Map 到通用目标包装器类型SumValueorg.bson.Document的原始AggregationResults
  • (8) 像\ <6>一样,可以直接从多个结果Document s 获得单个值。

Tip

您也可以将@AggregationReactive Repositories一起使用。

Note

简单类型的单结果检查返回的Document并检查以下内容:

  • 文档中只有一个条目,将其返回。

  • 有两个条目,一个是_id值。返回另一个。

  • 返回可分配给返回类型的第一个值。

  • 如果以上都不适用,则引发异常。

Warning

使用@Aggregation的存储库方法不支持Page返回类型。但是,您可以使用Pageable参数将$skip$limit$sort添加到管道。

14.4. CDI 整合

存储库接口的实例通常由容器创建,使用 Spring Data 时,Spring 是最自然的选择。从 1.3.0 版开始,Spring Data MongoDB 附带了一个自定义 CDI 扩展,使您可以在 CDI 环境中使用存储库抽象。该扩展是 JAR 的一部分。要激活它,请将 Spring Data MongoDB JAR 放到您的 Classpath 中。现在,您可以通过为MongoTemplate实现 CDI 生产者来设置基础结构,如以下示例所示:

class MongoTemplateProducer {

    @Produces
    @ApplicationScoped
    public MongoOperations createMongoTemplate() {

        MongoDbFactory factory = new SimpleMongoDbFactory(new MongoClient(), "database");
        return new MongoTemplate(factory);
    }
}

每当容器请求存储库类型的 bean 时,Spring Data MongoDB CDI 扩展都将MongoTemplate用作 CDI bean,并为 Spring Data 存储库创建代理。因此,获取 Spring Data 存储库的实例只需声明@Inject -ed 属性,如以下示例所示:

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

15.ReactiveMongoDB 存储库

本章介绍对 MongoDB 的反应式存储库支持的特殊性。本章以使用 Spring 数据存储库中解释的核心存储库支持为基础。您应该对这里介绍的基本概念有一个很好的了解。

15.1. 反应成分库

反应空间提供了各种反应成分库。最常见的库是RxJavaProject Reactor

Spring Data MongoDB 构建在MongoDB 反应流驱动程序的基础上,通过依赖Reactive Streams主动性来提供最大的互操作性。静态 API(例如ReactiveMongoOperations)是使用 Project Reactor 的FluxMono类型提供的。 Project Reactor 提供了各种适配器来转换 Reactive 包装器类型(从FluxObservable,反之亦然),但是转换很容易使您的代码混乱。

Spring Data 的 Repository 抽象是一个动态 API,主要由您和您在声明查询方法时的需求定义。通过从以下特定于库的存储库接口之一扩展,可以使用 RxJava 或 Project Reactor 包装器类型来实现 Reactive MongoDB 存储库:

  • ReactiveCrudRepository

  • ReactiveSortingRepository

  • RxJava2CrudRepository

  • RxJava2SortingRepository

Spring Data 在后台转换反应式包装器类型,以便您可以坚持使用自己喜欢的合成库。

15.2. Usage

要访问存储在 MongoDB 数据库中的域实体,您可以使用我们先进的存储库支持,从而大大简化了实现。为此,请为您的存储库创建一个类似的接口。但是,在执行此操作之前,您需要一个实体,例如以下示例中定义的实体:

例子 157.samplesPerson实体

public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

注意,在前面的示例中定义的实体具有名为id的类型ObjectId的属性。 MongoTemplate(支持存储库支持)中使用的默认序列化机制将名为id的属性视为文档 ID。目前,我们支持StringObjectIdBigInteger作为 id 类型。下面的示例演示如何创建一个接口,该接口定义对上一个示例中的Person对象的查询:

例子 158.持久化个人实体的基本存储库接口

public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

  Flux<Person> findByFirstname(String firstname);                                   (1)

  Flux<Person> findByFirstname(Publisher<String> firstname);                        (2)

  Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); (3)

  Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);       (4)

  Mono<Person> findFirstByLastname(String lastname);                                (5)
}
  • (1) 该方法显示针对具有给定lastname的所有人的查询。通过解析方法名称以获取可以与AndOr串联的约束来导出查询。因此,方法名称导致查询表达式{"lastname" : lastname}
  • (2) 一旦给定Publisher发出firstname,该方法就会显示一个查询所有给定firstname的人的查询。
  • (3) 使用Pageable将偏移量和排序参数传递给数据库。
  • (4) 为给定条件找到一个实体。对于非唯一结果,它以IncorrectResultSizeDataAccessException结尾。
  • (5) 除非\ <4>,否则即使查询产生更多的结果文档,也会始终发出第一个实体。

对于 Java 配置,请使用@EnableReactiveMongoRepositories注解。Comments 具有与名称空间元素相同的属性。如果未配置基本软件包,则基础结构将扫描带 Comments 的配置类的软件包。

Note

MongoDB 使用两种不同的驱动程序来进行命令式(同步/阻塞)和响应式(非阻塞)数据访问。您必须使用 Reactive Streams 驱动程序创建连接,以提供 Spring Data 的 Reactive MongoDB 支持所需的基础结构。因此,您必须为 MongoDB 的 Reactive Streams 驱动程序提供单独的配置。请注意,如果您使用反应式和阻塞式 Spring Data MongoDB 模板和存储库,则您的应用程序将在两个不同的连接上运行。

以下清单显示了如何对存储库使用 Java 配置:

例子 159.仓库的 Java 配置

@Configuration
@EnableReactiveMongoRepositories
class ApplicationConfig extends AbstractReactiveMongoConfiguration {

  @Override
  protected String getDatabaseName() {
    return "e-store";
  }

  @Override
  public MongoClient reactiveMongoClient() {
    return MongoClients.create();
  }

  @Override
  protected String getMappingBasePackage() {
    return "com.oreilly.springdata.mongodb"
  }
}

因为我们的域存储库扩展了ReactiveSortingRepository,所以它为您提供 CRUD 操作以及对实体进行排序访问的方法。使用存储库实例是一个依赖关系,将其注入 Client 端,如以下示例所示:

例子 160.对人物实体的排序访问

public class PersonRepositoryTests {

    @Autowired ReactivePersonRepository repository;

    @Test
    public void sortsElementsCorrectly() {
      Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));
    }
}

15.3. Features

与阻塞MongoDB Repositories相比,Spring Data 的 Reactive MongoDB 支持具有减少的功能集。

它支持以下功能:

15.3.1. 地理空间存储库查询

如您在前面的“ 地理空间存储库查询”中看到的,一些关键字触发了 MongoDB 查询中的地理空间操作。关键字Near允许进行进一步的修改,如下面的几个示例所示。

以下示例显示如何定义一个near查询,该查询查找具有给定点给定距离的所有人员:

例子 161.高级Near查询

public interface PersonRepository extends ReactiveMongoRepository<Person, String>

  // { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
  Flux<Person> findByLocationNear(Point location, Distance distance);
}

在查询方法中添加Distance参数可以将结果限制在给定距离内。如果设置的Distance包含Metric,我们将透明地使用$nearSphere而不是$code,如以下示例所示:

例子 162.将DistanceMetrics一起使用

Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}

Note

Reactive 地理空间存储库查询支持 Reactive 包装器类型内的域类型和GeoResult<T>结果。不支持GeoPageGeoResults,因为它们与延迟结果方法与预先计算平均距离相矛盾。但是,您仍然可以自己传递Pageable参数来页面结果。

DistanceMetric一起使用会导致添加$nearSphere(而不是普通的$near)子句。除此之外,实际距离将根据使用的Metrics计算得出。

(请注意,Metric不是指公制度量单位.它可能是英里而不是公里.相反,metric指的是度量系统的概念,无论使用哪种系统.)

Note

在目标属性上使用@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)会强制使用$nearSphere运算符。

Geo-near Queries

Spring Data MongoDB 支持近地查询,如以下示例所示:

public interface PersonRepository extends ReactiveMongoRepository<Person, String>

  // {'geoNear' : 'location', 'near' : [x, y] }
  Flux<GeoResult<Person>> findByLocationNear(Point location);

  // No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
  // Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
  //          'distanceMultiplier' : metric.multiplier, 'spherical' : true }
  Flux<GeoResult<Person>> findByLocationNear(Point location, Distance distance);

  // Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
  //          'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
  //          'spherical' : true }
  Flux<GeoResult<Person>> findByLocationNear(Point location, Distance min, Distance max);

  // {'geoNear' : 'location', 'near' : [x, y] }
  Flux<GeoResult<Person>> findByLocationNear(Point location);
}

15.3.2. 类型安全的查询方法

ReactiveMongoDB 存储库支持与Querydsl项目集成在一起,该项目提供了一种执行类型安全查询的方式。

Note

可以通过 Fluent 的 API 构造查询,而不是将查询编写为内联字符串或将其外部化为 XML 文件。

— Querydsl 团队

它提供以下功能:

  • IDE 中的代码完成(可以在您喜欢的 Java IDE 中扩展所有属性,方法和操作)。

  • 几乎不允许语法上无效的查询(在所有级别上都是类型安全的)。

  • 可以安全地引用域类型和属性-不涉及任何字符串!

  • 更好地适应重构域类型的更改。

  • 增量查询定义更容易。

有关如何使用 Maven 或 Ant 引导环境以生成基于 APT 的代码的信息,请参见Querydsl documentation

Querydsl 存储库支持使您可以编写和执行查询,例如以下内容:

QPerson person = QPerson.person;

Flux<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

QPerson是由 Java 注解后处理工具生成的类。它是Predicate,可让您编写类型安全的查询。请注意,查询中除C0123值外没有其他字符串。

您可以通过ReactiveQuerydslPredicateExecutor接口使用生成的Predicate类,以下清单显示了该接口:

例子 163.响应式 Querydsl 的网关-ReactiveQuerydslPredicateExecutor

public interface ReactiveQuerydslPredicateExecutor<T> {

	Mono<T> findOne(Predicate predicate);

	Flux<T> findAll(Predicate predicate);

	Flux<T> findAll(Predicate predicate, Sort sort);

	Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

	Flux<T> findAll(OrderSpecifier<?>... orders);

	Mono<Long> count(Predicate predicate);

	Mono<Boolean> exists(Predicate predicate);
}

要在存储库实现中使用它,请将其添加到继承接口的存储库接口列表中,如以下示例所示:

例子 164.响应式 Querydsl 存储库声明

public interface PersonRepository extends ReactiveMongoRepository<Person, String>, ReactiveQuerydslPredicateExecutor<Person> {

   // additional query methods go here
}

Note

请注意,Reactive MongoDB 支持不支持联接(DBRef)。

16. Auditing

16.1. Basics

Spring Data 提供了复杂的支持,可以透明地跟踪创建或更改实体的人员以及更改发生的时间。要利用该功能,您必须为实体类配备审核元数据,该审核元数据可以使用注解或通过实现接口来定义。

16.1.1. 基于 Comments 的审核元数据

我们提供@CreatedBy@LastModifiedBy来捕获创建或修改实体的用户,提供@CreatedDate@LastModifiedDate来捕获更改发生的时间。

例子 165.被审计实体

class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

如您所见,可以根据要捕获的信息有选择地应用 Comments。进行更改时捕获的 Comments 可以用于 Joda-Time 类型,DateTime,旧版 Java DateCalendar,JDK8 日期和时间类型以及longLong的属性。

16.1.2. 基于接口的审核元数据

如果您不想使用 Comments 来定义审核元数据,则可以让您的域类实现Auditable接口。它公开了所有审核属性的设置器方法。

还有一个方便的 Base ClassAbstractAuditable,您可以对其进行扩展,以避免需要手动实现接口方法。这样做会增加您的域类与 Spring Data 的耦合,这可能是您要避免的事情。通常,首选基于 Comments 的方式来定义审计元数据,因为它侵入性较小且更灵活。

16.1.3. AuditorAware

如果您使用@CreatedBy@LastModifiedBy,则审计基础结构需要以某种方式了解当前的主体。为此,我们提供了一个AuditorAware<T> SPI 接口,您必须实现该接口来告知基础结构与应用程序交互的当前用户或系统是谁。通用类型T定义必须使用@CreatedBy@LastModifiedByComments 的属性的类型。

以下示例显示了使用 Spring Security 的Authentication对象的接口的实现:

例子 166.基于 Spring Security 的 AuditorAware 的实现

class SpringSecurityAuditorAware implements AuditorAware<User> {

  public Optional<User> getCurrentAuditor() {

    return Optional.ofNullable(SecurityContextHolder.getContext())
			  .map(SecurityContext::getAuthentication)
			  .filter(Authentication::isAuthenticated)
			  .map(Authentication::getPrincipal)
			  .map(User.class::cast);
  }
}

该实现访问 Spring Security 提供的Authentication对象,并查找您在UserDetailsService实现中创建的自定义UserDetails实例。我们在这里假设您是通过UserDetails实现公开域用户的,但是根据找到的Authentication,您还可以从任何地方查找它。

16.2. MongoDB 的常规审核配置

要激活审计功能,请将 Spring Data Mongo auditing名称空间元素添加到您的配置中,如以下示例所示:

例子 167.使用 XML 配置激活审计

<mongo:auditing mapping-context-ref="customMappingContext" auditor-aware-ref="yourAuditorAwareImpl"/>

从 Spring Data MongoDB 1.4 开始,可以通过使用@EnableMongoAuditingComments 对配置类进行 Comments 来启用审计,如以下示例所示:

例子 168.使用 JavaConfig 激活审计

@Configuration
@EnableMongoAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> myAuditorProvider() {
      return new AuditorAwareImpl();
  }
}

如果将AuditorAware类型的 bean 暴露给ApplicationContext,则审计基础结构会自动选择它并使用它来确定要在域类型上设置的当前用户。如果在ApplicationContext中注册了多个实现,则可以通过显式设置@EnableMongoAuditingauditorAwareRef属性来选择要使用的实现。

17. Mapping

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

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

17.1. 对象 Map 基础

本节介绍了 Spring Data 对象 Map,对象创建,字段和属性访问,可变性和不可变性的基础。请注意,本部分仅适用于不使用基础数据存储(例如 JPA)的对象 Map 的 Spring Data 模块。另外,请确保参考 Store 特定的部分以获取 Store 特定的对象 Map,例如索引,自定义列或字段名称等。

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

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

  • 实例填充以实现所有暴露的属性。

17.1.1. 对象创建

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

  • 如果有一个无参数的构造函数,则将使用它。其他构造函数将被忽略。

  • 如果只有一个构造函数接受参数,则将使用它。

  • 如果有多个构造函数采用参数,则必须由@PersistenceConstructorCommentsSpring Data 要使用的一个。

值解析假定构造函数参数名称与实体的属性名称匹配,即,解析将像要填充该属性一样执行,包括 Map 中的所有自定义项(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或构造器上存在的@ConstructorPropertiesComments。

可以使用特定于 Store 的 SpEL 表达式使用 Spring Framework 的@Value值 Comments 来自定义值分辨率。请参阅有关 Store 特定 Map 的部分以获取更多详细信息。

对象创建内部

为了避免反射的开销,Spring Data 对象的创建使用默认情况下在运行时生成的工厂类,该工厂类将直接调用域类的构造函数。即对于此示例类型:

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

我们将在运行时创建一个在语义上等效于该类的工厂类:

class PersonObjectInstantiator implements ObjectInstantiator {

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

与反射相比,这使我们的性能提高了约 10%。为了使域类有资格进行此类优化,它需要遵守一组约束:

  • 它不能是私人类

  • 它不能是非静态内部类

  • 它不能是 CGLib 代理类

  • Spring Data 使用的构造函数不能为私有

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

17.1.2. 财产人口

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

  • 如果属性是不可变的,但是公开了一个凋零方法(请参见下文),我们将使用凋零来创建具有新属性值的新实体实例。

  • 如果定义了属性访问(即通过 getter 和 setter 的访问),那么我们正在调用 setter 方法。

  • 默认情况下,我们直接设置字段值。

房地产人口内部

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

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}

例子 169.生成的属性访问器

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) 默认情况下,Spring Data 使用字段访问来读取和写入属性值。根据private个字段的可见性规则,MethodHandles用于与字段进行交互。
  • (3) 该类公开了用于设置标识符的withId(…)方法,例如将实例插入数据存储区并已生成标识符时。调用withId(…)将创建一个新的Person对象。所有后续突变都将在新实例中发生,而先前的实例将保持不变。
  • (4) 使用属性访问可直接调用方法,而无需使用MethodHandles

这使我们的反射性能提高了约 25%。为了使域类有资格进行此类优化,它需要遵守一组约束:

  • 类型不得位于默认值或java包下。

  • 类型及其构造函数必须为public

  • 内部类的类型必须为static

  • 使用的 Java 运行时必须允许在原始ClassLoader中声明类。 Java 9 和更高版本强加了某些限制。

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

让我们看一下以下实体:

例子 170.一个 samples 实体

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

  void setRemarks(String remarks) {                                         (5)
    this.remarks = remarks;
  }
}
  • (1) 标识符属性是 final,但在构造函数中设置为null。该类公开了用于设置标识符的withId(…)方法,例如将实例插入数据存储区并已生成标识符时。创建新实例时,原始Person实例保持不变。通常将相同的模式应用于由存储 Management 的其他属性,但可能需要为持久性操作进行更改。
  • (2) firstnamelastname属性是可能通过 getter 公开的普通不可变属性。
  • (3) age属性是不可变的,但是从birthday属性派生的。按照所示的设计,由于 Spring Data 使用唯一声明的构造函数,因此数据库值将胜过默认值。即使意图是首选计算,此构造函数也必须将age作为参数(可能会忽略它),这很重要,否则属性填充步骤将尝试设置 age 字段并由于其不可变而失败,并且没有凋谢。
  • (4) 通过直接设置其comment属性是可变的。
  • (5) remarks属性是可变的,可通过直接设置comment字段或通过调用 setter 方法来填充
  • (6) 该类公开了一个工厂方法和一个用于创建对象的构造函数。这里的核心思想是使用工厂方法而不是其他构造函数,以避免通过@PersistenceConstructor消除构造函数歧义的需要。相反,属性的默认设置是在工厂方法中处理的。

17 .1.3. 一般建议

  • 尝试坚持不可变的对象-不可变的对象很容易创建,因为实现一个对象只需调用其构造函数即可。同样,这避免了用允许 Client 端代码操纵对象状态的 setter 方法乱扔您的域对象。如果需要它们,则最好使它们受到程序包保护,以便只能由有限数量的同一位置类型调用它们。仅限构造函数的实现比属性填充快 30%。

  • 提供一个全参数的构造函数-即使您无法或不希望将实体建模为不可变值,提供构造函数仍具有价值,该构造函数将实体的所有属性作为参数,包括可变的允许对象 Map 跳过属性填充以获得最佳性能。

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

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

  • 对于要生成的标识符,请结合使用 final 字段和凋灵方法

  • 使用 Lombok 避免样板代码-由于持久性操作通常需要构造函数使用所有参数,因此它们的声明成为对字段分配的样板参数的繁琐重复,最好使用 Lombok 的@AllArgsConstructor来避免。

17.1.4. Kotlin 支持

Spring Data 修改了 Kotlin 的细节以允许对象创建和变异。

Kotlin 对象创建

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

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

上面的类使用显式构造函数编译为典型类。我们可以通过添加另一个构造函数并使用@PersistenceConstructor对其进行 Comments 来表示该构造函数的首选项来自定义此类:

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

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

Kotlin 通过允许在未提供参数的情况下使用默认值来支持参数可选性。当 Spring Data 检测到带有参数默认值的构造函数时,如果数据存储区不提供值(或仅返回null),它将保留这些参数不存在,因此 Kotlin 可以应用参数默认值。考虑下面的类,该类对name应用参数默认值

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

每次name参数不是结果的一部分或它的值是null时,name默认为unknown

Kotlin 数据类别的属性人口

在 Kotlin 中,所有类默认都是不可变的,并且需要显式的属性声明来定义可变属性。考虑以下dataPerson

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

该类实际上是不可变的。当 Kotlin 生成copy(…)方法时,它可以创建新实例,该方法创建新对象实例,该对象实例从现有对象复制所有属性值,并将作为参数提供的属性值应用于该方法。

17.2. 基于约定的 Map

MappingMongoConverter有一些约定,用于在不提供其他 Map 元数据的情况下将对象 Map 到文档。约定是:

  • 简短的 Java 类名称以以下方式 Map 到集合名称。类别com.bigbank.SavingsAccountMap 到savingsAccount集合名称。

  • 所有嵌套对象都作为嵌套对象存储在文档中,而不是作为 DBRef 存储。

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

  • 对象的字段用于在文档中的字段之间来回转换。不使用公开的JavaBean属性。

  • 如果您有一个非零参数构造函数,其构造函数参数名称与文档的顶级字段名称匹配,则使用该构造函数。否则,将使用零参数构造函数。如果有多个非零参数构造函数,则将引发异常。

17.2.1. _id 字段在 Map 层中的处理方式。

MongoDB 要求所有文档都具有一个_id字段。如果不提供,驱动程序将为 ObjectId 分配一个生成的值。 “ _id”字段可以是数组以外的任何类型,只要它是唯一的即可。该驱动程序自然支持所有原始类型和日期。使用MappingMongoConverter时,有一些规则控制 Java 类中的属性如何 Map 到此_id字段。

下面概述了将哪个字段 Map 到_id文档字段:

  • @Id(org.springframework.data.annotation.Id)Comments 的字段将被 Map 到_id字段。

  • 没有 Comments 但名为id的字段将被 Map 到_id字段。

  • 标识符的默认字段名称是_id,可以通过@FieldComments 进行自定义。

表 11. _id字段定义的转换示例

Field definition MongoDB 中的结果 ID 字段名
String ID _id
@Field String ID _id
@Field("x") String ID x
@Id String x _id
@Field("x") @Id String x _id

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

  • 如果在 Java 类中将名为id的字段声明为 String 或 BigInteger,则将在可能的情况下将其转换为 ObjectId 并存储为 ObjectId。 ObjectId 作为字段类型也是有效的。如果您在应用程序中为id指定一个值,则 MongoDB 驱动程序会检测到对 ObjectId 的转换。如果指定的id值不能转换为 ObjectId,则该值将按原样存储在文档的_id 字段中。如果该字段用@IdComments,则这也适用。

  • 如果在 Java 类中用@MongoIdComments 字段,则将使用其实际类型将其转换并存储为该字段。除非@MongoId声明所需的字段类型,否则不会进行进一步的转换。

  • 如果在 Java 类中用@MongoId(FieldType.…)Comments 字段,则将尝试将该值转换为声明的FieldType.

  • 如果在 Java 类中未将名为id id 的字段声明为 String,BigInteger 或 ObjectID,则应在应用程序中为其分配一个值,以便可以按原样存储在文档的_id 字段中。

  • 如果 Java 类中没有名为id的字段,则驱动程序将生成一个隐式_id文件,但不会 Map 到 Java 类的属性或字段。

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

17.3. 数据 Map 和类型转换

本部分说明如何在 MongoDB 表示形式之间 Map 类型。 Spring Data MongoDB 支持所有可以表示为 BSON(MongoDB 的内部文档格式)的类型。除了这些类型之外,Spring Data MongoDB 还提供了一组内置转换器来 Map 其他类型。您可以提供自己的转换器来调整类型转换。有关更多详细信息,请参见使用显式转换器覆盖 Map

以下提供每种可用类型转换的示例:

表 12.类型

Type Type conversion Sample
String native {"firstname" : "Dave"}
double , Double , float , Float native {"weight" : 42.5}
int , Integer , short , Short native

32-bit integer
{"height" : 42}
longLong 本机
64-bit integer
{"height" : 42}
DateTimestamp 本地 {"date" : ISODate("2019-11-12T23:00:00.809Z")}
byte[] native {"bin" : { "$binary" : "AQIDBA==", "$type" : "00" }}
java.util.UUID(旧版 UUID) 本地 {"uuid" : { "$binary" : "MEaf1CFQ6lSphaa3b9AtlA==", "$type" : "03" }}
Date native {"date" : ISODate("2019-11-12T23:00:00.809Z")}
ObjectId native {"_id" : ObjectId("5707a2690364aba3136ab870")}
数组,ListBasicDBList 本机 {"cookies" : [ … ]}
booleanBoolean 本地 {"active" : true}
null native {"value" : null}
Document native {"value" : { … }}
Decimal128 native {"value" : NumberDecimal(…)}
AtomicInteger
在实际转化之前先致电get()
32-bit integer
{"value" : "741" }
AtomicLong
在实际转化之前先致电get()
64-bit integer
{"value" : "741" }
BigInteger converter
String
{"value" : "741" }
BigDecimal converter
String
{"value" : "741.99" }
URL converter {"website" : "https://projects.spring.io/spring-data-mongodb/" }
Locale converter {"locale : "en_US" }
charCharacter 转换器 {"char" : "a" }
NamedMongoScript converter
Code
{"_id" : "script name", value: (some javascript code) }
java.util.Currency converter {"currencyCode" : "EUR"}
LocalDate
(Joda,Java 8,JSR310-BackPort)
转换器 {"date" : ISODate("2019-11-12T00:00:00.000Z")}
LocalDateTimeLocalTimeInstant
(Joda,Java 8,JSR310-BackPort)
转换器 {"date" : ISODate("2019-11-12T23:00:00.809Z")}
DateTime(Joda) 转换器 {"date" : ISODate("2019-11-12T23:00:00.809Z")}
ZoneId(Java 8,JSR310-BackPort) 转换器 {"zoneId" : "ECT - Europe/Paris"}
Box converter {"box" : { "first" : { "x" : 1.0 , "y" : 2.0} , "second" : { "x" : 3.0 , "y" : 4.0}}
Polygon converter {"polygon" : { "points" : [ { "x" : 1.0 , "y" : 2.0} , { "x" : 3.0 , "y" : 4.0} , { "x" : 4.0 , "y" : 5.0}]}}
Circle converter {"circle" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}
Point converter {"point" : { "x" : 1.0 , "y" : 2.0}}
GeoJsonPoint converter {"point" : { "type" : "Point" , "coordinates" : [3.0 , 4.0] }}
GeoJsonMultiPoint converter {"geoJsonLineString" : {"type":"MultiPoint", "coordinates": [ [ 0 , 0 ], [ 0 , 1 ], [ 1 , 1 ] ] }}
Sphere converter {"sphere" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}
GeoJsonPolygon converter {"polygon" : { "type" : "Polygon", "coordinates" : [[ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 1 ], [ 0 , 0 ] ]] }}
GeoJsonMultiPolygon converter {"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [ [ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ], [ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ] ] }}
GeoJsonLineString converter { "geoJsonLineString" : { "type" : "LineString", "coordinates" : [ [ 40 , 5 ], [ 41 , 6 ] ] }}
GeoJsonMultiLineString converter {"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [ [ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ], [ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ] ] }}

17.4. Map 配置

除非明确配置,否则在创建MongoTemplate时会默认创建MappingMongoConverter实例。您可以创建自己的MappingMongoConverter实例。这样做可以让您指示可以在 Classpath 中的域类中找到哪些位置,以便 Spring Data MongoDB 可以提取元数据并构造索引。另外,通过创建自己的实例,您可以注册 Spring 转换器以将特定的类与数据库进行 Map。

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

例子 171. @Configuration 类来配置 MongoDBMap 支持

@Configuration
public class GeoSpatialAppConfig extends AbstractMongoConfiguration {

  @Bean
  public MongoClient mongoClient() {
    return new MongoClient("localhost");
  }

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

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

  // the following are optional

  @Bean
  @Override
  public CustomConversions customConversions() throws Exception {
    List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
    converterList.add(new org.springframework.data.mongodb.test.PersonReadConverter());
    converterList.add(new org.springframework.data.mongodb.test.PersonWriteConverter());
    return new CustomConversions(converterList);
  }

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

AbstractMongoConfiguration要求您实现定义com.mongodb.MongoClient并提供数据库名称的方法。 AbstractMongoConfiguration还有一个名为getMappingBasePackage(…)的方法,您可以重写该方法以告诉转换器在何处扫描带有@DocumentComments 的类。

您可以通过覆盖customConversions方法将其他转换器添加到转换器。在前面的示例中还显示了LoggingEventListener,该日志记录了已发布到 Spring 的ApplicationContextEvent基础结构上的MongoMappingEvent实例。

Note

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

使用 Spring 的 MongoDB 名称空间,您可以启用 XML 的 Map 功能,如以下示例所示:

例子 172. XML 模式来配置 MongoDBMap 支持

<?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"/>

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

base-package属性告诉它在哪里扫描使用@org.springframework.data.mongodb.core.mapping.DocumentCommentsComments 的类。

17.5. 基于元数据的 Map

要充分利用 Spring Data MongoDB 支持内的对象 Map 功能,您应该使用@DocumentComments 对 Map 的对象进行 Comments。尽管 Map 框架不必具有此注解(即使没有任何注解也可以正确 Map 您的 POJO),但它可以让 Classpath 扫描程序查找并预处理您的域对象以提取必要的元数据。如果不使用此注解,则在您第一次存储域对象时,应用程序的性能就会受到影响,因为 Map 框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何坚持下去。以下示例显示了一个域对象:

例子 173.例子领域对象

package com.mycompany.domain;

@Document
public class Person {

  @Id
  private ObjectId id;

  @Indexed
  private Integer ssn;

  private String firstName;

  @Indexed
  private String lastName;
}

Tip

@Id注解告诉 Map 器您要对 MongoDB _id属性使用哪个属性,而@Indexed注解告诉 Map 框架在文档的该属性上调用createIndex(…),从而加快搜索速度。自动索引创建仅对带有@DocumentComments 的类型完成。

Note

要关闭*自动创建索引功能,请在您的配置中覆盖autoIndexCreation()

@Configuration
public class Config extends AbstractMongoClientConfiguration {

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

// ...
}

Tip

在 3.x 发行版中,默认情况下自动创建索引将被关闭。我们建议索引创建可以带外进行,也可以作为使用IndexOperations启动应用程序的一部分进行。

17.5.1. MapComments 概述

MappingMongoConverter 可以使用元数据来驱动对象到文档的 Map。可以使用以下 Comments:

  • @Id:在字段级别应用,以标记用于标识目的的字段。

  • @MongoId:在字段级别应用,以标记用于标识目的的字段。接受可选的FieldType以自定义 ID 转换。

  • @Document:在类级别应用,以指示该类是 Map 到数据库的候选对象。您可以指定将在其中存储数据的集合的名称。

  • @DBRef:在字段上应用以指示将使用 com.mongodb.DBRef 存储它。

  • @Indexed:在字段级别应用,以描述如何索引字段。

  • @CompoundIndex(可重复):在类型级别应用以声明复合索引。

  • @GeoSpatialIndexed:在字段级别应用,以描述如何对字段进行地理索引。

  • @TextIndexed:在字段级别应用,以标记要包含在文本索引中的字段。

  • @HashIndexed:在字段级别应用,以在哈希索引中使用以在分片群集中对数据进行分区。

  • @Language:在字段级别应用,以设置文本索引的语言覆盖属性。

  • @Transient:默认情况下,所有私有字段都 Map 到文档,此 Comments 将应用该字段的字段从数据库中存储出来

  • @PersistenceConstructor:标记给定的构造函数-甚至是受保护的程序包-在从数据库实例化对象时使用。构造函数自变量按名称 Map 到检索到的 Document 中的键值。

  • @Value:此 Comments 是 Spring 框架的一部分。在 Map 框架内,它可以应用于构造函数参数。这样,您就可以使用 Spring Expression Language 语句在用于构造域对象之前转换在数据库中检索到的键的值。为了引用给定文档的属性,必须使用类似@Value("#root.myProperty")的表达式,其中root表示给定文档的根。

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

  • @Version:在字段级别应用,用于乐观锁定,并检查是否对保存操作进行了修改。初始值为zero(对于基本类型为one),该值在每次更新时都会自动增加。

Map 元数据基础结构是在一个与技术无关的单独的 spring-data-commons 项目中定义的。 MongoDB 支持中使用了特定的子类来支持基于 Comments 的元数据。如果有需求,也可以采用其他策略。

这是更复杂的 Map 的示例。

@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

Tip

当 Map 基础结构推断的本机 MongoDB 类型与预期的不匹配时,@Field(targetType=…)会派上用场。就像BigDecimal一样,它表示为String而不是Decimal128,只是因为早期版本的 MongoDB Server 不支持它。

public class Balance {

@Field(targetType = DECIMAL128)
private BigDecimal value;

// ...
}

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

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

// ...

public class Balance {

@Decimal128
private BigDecimal value;

// ...
}

17.5.2. 定制对象构造

Map 子系统通过使用@PersistenceConstructorComments 对构造函数进行 Comments,从而允许自定义对象构造。通过以下方式解析用于构造函数参数的值:

  • 如果使用@ValueComments 对参数进行 Comments,则将评估给定的表达式并将结果用作参数值。

  • 如果 Java 类型具有名称与 Importing 文档的给定字段匹配的属性,则使用其属性信息来选择适当的构造函数参数,以将 Importing 字段值传递给该属性。仅当参数名称信息存在于 Java .class文件中时才有效,这可以通过使用调试信息编译源代码或使用 Java 8 中 Javac 的新-parameters命令行开关来实现。

  • 否则将抛出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);

Note

如果无法解析给定的属性路径,则quantity参数的@ValueComments 中的 SpEL 表达式会回落到值0

MappingMongoConverterUnitTests测试套件中可以找到使用@PersistenceConstructor注解的其他示例。

17.5.3. 复合索引

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

Note

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

这是一个创建升序为lastName且降序为age的复合索引的示例:

例子 174.复合索引用法的例子

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;

}

Tip

@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.4. 散列索引

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

这是一个为_id创建哈希索引的示例:

例子 175.哈希索引用法的例子

@Document
public class DomainType {

  @HashIndexed @Id String id;

  // ...
}

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

例子 176.例子哈希索引用法与简单索引一起

@Document
public class DomainType {

  @Indexed
  @HashIndexed
  String value;

  // ...
}

如果上面的示例过于冗长,则使用复合 Comments 可以减少需要在属性上声明的 Comments 的数量:

例子 177.例子组成的散列索引用法

@Document
public class DomainType {

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

  // ...
}

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

  @AliasFor(annotation = Indexed.class, attribute = "name") (1)
  String name() default "";
}
  • (1) 可能为元 Comments 的某些属性注册别名。

Note

尽管通过 Comments 创建索引在许多情况下都很方便,但请考虑通过IndexOperations手动设置索引来接管更多控制。

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

17.5.5. Literals 索引

Note

默认情况下,MongoDB v.2.4 禁用文本索引功能。

创建文本索引可以将多个字段累积到可搜索的全文索引中。每个集合只能有一个文本索引,因此所有标有@TextIndexed的字段都将合并到该索引中。可以对属性进行加权,以影响文档得分以对结果进行排名。文本索引的默认语言是英语。要更改默认语言,请将language属性设置为所需的任何语言(例如@Document(language="spanish"))。使用称为language@Language的属性,可以在每个文档基础上定义语言替代。以下示例显示了如何创建文本索引并将语言设置为西班牙语:

例子 178.文本索引用法的例子

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

    @TextIndexed String foo;

    @Language String lang;

    Nested nested;
}

class Nested {

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

17.5.6. 使用 DBRef

Map 框架不必存储嵌入文档中的子对象。您也可以单独存储它们,并使用 DBRef 引用该文档。从 MongoDB 加载对象时,将急切解析这些引用,以便您获得一个 Map 的对象,该对象看起来像是已嵌入到主文档中一样。

下面的示例使用一个 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;
}

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

Tip

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

DBRef s 也可以延迟解决。在这种情况下,对属性的实际ObjectCollection引用将在首次访问属性时解析。使用@DBReflazy属性进行指定。还定义为延迟加载DBRef并用作构造函数参数的必需属性也使用延迟加载代理进行修饰,以确保对数据库和网络的压力尽可能小。

Tip

延迟加载的DBRef可能很难调试。确保工具不会意外触发代理解析,例如。调用toString()或某些内联调试渲染调用属性 getter。请考虑为org.springframework.data.mongodb.core.convert.DefaultDbRefResolver启用* trace *日志记录以了解DBRef分辨率。

17.5.7. Map 框架事件

在 Map 过程的整个生命周期中都会触发事件。 Lifecycle Events部分对此进行了说明。

在 Spring ApplicationContext 中声明这些 bean 会使事件在分派时被调用。

17.5.8. 使用显式转换器覆盖 Map

在存储和查询对象时,让MongoConverter实例处理所有 Java 类型到Document实例的 Map 很方便。但是,有时您可能希望MongoConverter实例完成大部分工作,但让您有选择地处理特定类型的转换-可能是为了优化性能。

要有选择地自己处理转换,请向MongoConverter注册一个或多个一个或多个org.springframework.core.convert.converter.Converter实例。

Note

Spring 3.0 引入了 core.convert 包,该包提供了通用的类型转换系统。在 Spring 参考文档章节“Spring 类型转换”中对此进行了详细描述。

您可以使用AbstractMongoConfiguration中的customConversions方法来配置转换器。 在本章开始示例显示了如何使用 Java 和 XML 执行配置。

下面的 Spring Converter 实现示例将Document转换为Person POJO:

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

以下示例从Person转换为Document

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

18. Kotlin 支持

Kotlin是针对 JVM(和其他平台)的静态类型语言,它允许编写简洁明了的代码,同时为使用 Java 编写的现有库提供出色的interoperability

Spring Data 为 Kotlin 提供了一流的支持,并且使开发人员几乎可以将 Spring Data 视为 Kotlin 本机框架来编写 Kotlin 应用程序。

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

18.1. Requirements

Spring Data 支持 Kotlin 1.3,并要求kotlin-stdlib(或其变体之一,例如kotlin-stdlib-jdk8)和kotlin-reflect出现在 Classpath 中。如果您通过start.spring.io引导 Kotlin 项目,则默认情况下会提供这些功能。

18.2. 空安全

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

尽管 Java 不允许您在其类型系统中表示空安全性,但是 Spring Data API 会在org.springframework.lang包中声明的JSR-305对工具友好的 Comments 中进行 Comments。默认情况下,Kotlin 中使用的 Java API 中的类型被识别为platform types,对此它们的空检查得到了放宽。 Kotlin 对 JSR-305 注解的支持和 Spring 可空性 Comments 为 Kotlin 开发人员提供了整个 Spring Data API 的空安全性,具有在编译时处理null相关问题的优势。

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

Tip

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

对于 Kotlin 1.1 版,默认行为与-Xjsr305=warn相同。考虑到 Spring Data API 的空安全性,必须提供strict值。 Kotlin 类型是从 Spring API 推断得出的,但应在知道 Spring API 可以声明无效性的情况下使用,即使在次要发行版之间,并且将来可能会添加更多检查。

Note

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

18.3. 对象 Map

有关如何实现 Kotlin 对象的详细信息,请参见Kotlin support

18.4. Extensions

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

Note

请记住,必须导入 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 中的characters是强类型的,但是 Kotlin 的聪明类型推断允许使用较短的语法。

Spring Data MongoDB 提供了以下扩展:

  • 归一化的泛型支持MongoOperationsReactiveMongoOperationsFluentMongoOperationsReactiveFluentMongoOperationsCriteria

  • Kotlin 的类型安全查询

  • CoroutinesextensionsReactiveFluentMongoOperations

18.5. Coroutines

Kotlin Coroutines是轻量级线程,允许强制性地编写非阻塞代码。在语言方面,suspend函数提供了异步操作的抽象,而在库方面kotlinx.coroutines提供了诸如async { }的函数和Flow的类型。

Spring Data 模块在以下范围提供对协程的支持:

18.5.1. Dependencies

kotlinx-coroutines-corekotlinx-coroutines-reactor依赖项在 Classpath 中时,将启用协程支持:

例子 179.在 Maven pom.xml 中添加的依赖项

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

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

Note

受支持的版本1.3.0及更高版本。

18.5.2. Reactive 如何转换为协程?

对于返回值,以下是从 Reactive 到 Coroutines 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>

Flow在协程世界中等效为Flux,适用于热流或冷流,有限流或无限流,主要区别在于:

  • Flow是基于推的,而Flux是推拉混合的

  • 背压通过暂停功能实现

  • Flow只有单吊收法,并且运算符实现为extensions

  • 操作员易于实施感谢协程

  • 扩展允许将自定义运算符添加到Flow

  • 收集操作正在暂停功能

  • map operator支持异步操作(不需要flatMap),因为它采用了挂起函数参数

阅读有关与 Spring,协程和 Kotlin 流程反应的博客文章,了解更多详细信息,包括如何与 Coroutines 同时运行代码。

19.跨 Store 支持

Warning

此功能已被弃用,将被删除而无需替换。

有时您需要将数据存储在多个数据存储中,并且这些数据存储必须具有不同的类型。一个可能是关系型的,另一个可能是文档存储的。对于此用例,我们在 MongoDB 支持中创建了一个单独的模块,用于处理所谓的“跨 Store 支持”。当前的实现基于 JPA 作为关系数据库的驱动程序,我们让实体中的选择字段存储在 Mongo 数据库中。除了让您将数据存储在两个存储中之外,我们还将非事务性 MongoDB 存储的持久性操作与关系数据库的事务生命周期进行协调。

19.1. 跨 Store 配置

假设您有一个运行中的 JPA 应用程序,并且想为 MongoDB 添加一些跨存储的持久性,那么您必须在配置中添加什么?

首先,您需要在交叉存储模块上添加依赖项。如果使用 Maven,则可以将以下依赖项添加到 pom 中:

例子 180.具有spring-data-mongodb-cross-store依赖性的例子 Maven pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  ...

    <!-- Spring Data -->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-mongodb-cross-store</artifactId>
      <version>${spring.data.mongo.version}</version>
    </dependency>

  ...

</project>

添加依赖项后,需要为项目启用 AspectJ。跨存储支持是通过 AspectJ 方面实现的,因此,如果启用编译时 AspectJ 支持,则跨存储功能可用于您的项目。在 Maven 中,您可以向 pom 的<build>部分添加一个附加插件,如下所示:

例子 181.启用 AspectJ 插件的 Maven pom.xml 例子

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  ...

  <build>
    <plugins>

      …

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.0</version>
        <dependencies>
          <!-- NB: You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) -->
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
        </dependencies>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <outxml>true</outxml>
          <aspectLibraries>
            <aspectLibrary>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
            <aspectLibrary>
              <groupId>org.springframework.data</groupId>
              <artifactId>spring-data-mongodb-cross-store</artifactId>
            </aspectLibrary>
          </aspectLibraries>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>

      ...

    </plugins>
  </build>

...

</project>

最后,您需要配置您的项目以使用 MongoDB,还需要配置使用哪些方面。您应该将以下 XML 代码段添加到您的应用程序上下文中:

例子 182.具有 MongoDB 和跨 Store 方面支持的例子应用程序上下文

<?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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jpa="http://www.springframework.org/schema/data/jpa"
  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/jdbc
    https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/data/jpa
    https://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">

  ...

  <!--  Mongo config -->
  <mongo:mongo-client host="localhost" port="27017"/>

  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoClient" ref="mongoClient"/>
    <constructor-arg name="databaseName" value="test"/>
    <constructor-arg name="defaultCollectionName" value="cross-store"/>
  </bean>

  <bean class="org.springframework.data.mongodb.core.MongoExceptionTranslator"/>

  <!--  Mongo cross-store aspect config -->
  <bean class="org.springframework.data.persistence.document.mongo.MongoDocumentBacking"
        factory-method="aspectOf">
    <property name="changeSetPersister" ref="mongoChangeSetPersister"/>
  </bean>
  <bean id="mongoChangeSetPersister"
      class="org.springframework.data.persistence.document.mongo.MongoChangeSetPersister">
    <property name="mongoTemplate" ref="mongoTemplate"/>
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>

  ...

</beans>

19.2. 编写跨 Store 应用程序

我们假设您有一个运行中的 JPA 应用程序,因此我们仅涉及将部分实体持久保存在 Mongo 数据库中所需的其他步骤。为此,您需要确定要保留的字段。它应该是一个域类,并遵循前面各章中介绍的 MongoMap 支持的一般规则。您要在 MongoDB 中保留的字段应使用@RelatedDocumentComments 进行 Comments。这实际上就是您需要做的。跨 Store 方面负责其余的工作,包括:

  • @Transient标记该字段,以便 JPA 不会保留该字段

  • 跟踪对字段值所做的任何更改,并在成功完成事务后将其写入数据库

  • 首次在应用程序中使用该值时,从 MongoDB 加载文档。

以下示例显示了一个实体,该实体的字段带有@RelatedDocumentComments:

例子 183.具有@RelatedDocument 的实体的例子

@Entity
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String firstName;

  private String lastName;

  @RelatedDocument
  private SurveyInfo surveyInfo;

  // getters and setters omitted
}

以下示例显示了要存储为Document的域类:

例子 184.一个要存储为文档的领域类的例子

public class SurveyInfo {

  private Map<String, String> questionsAndAnswers;

  public SurveyInfo() {
    this.questionsAndAnswers = new HashMap<String, String>();
  }

  public SurveyInfo(Map<String, String> questionsAndAnswers) {
    this.questionsAndAnswers = questionsAndAnswers;
  }

  public Map<String, String> getQuestionsAndAnswers() {
    return questionsAndAnswers;
  }

  public void setQuestionsAndAnswers(Map<String, String> questionsAndAnswers) {
    this.questionsAndAnswers = questionsAndAnswers;
  }

  public SurveyInfo addQuestionAndAnswer(String question, String answer) {
    this.questionsAndAnswers.put(question, answer);
    return this;
  }
}

在前面的示例中,在Customer对象上设置了SurveyInfo后,先前配置的MongoTemplate用于将SurveyInfo(以及有关 JPA 实体的一些元数据)保存在以其全限定名命名的 MongoDB 集合中。 JPA 实体类。以下代码显示了如何配置 JPA 实体以实现 MongoDB 的跨存储持久性:

例子 185.使用为跨 Store 持久性配置的 JPA 实体的代码例子

Customer customer = new Customer();
customer.setFirstName("Sven");
customer.setLastName("Olafsen");
SurveyInfo surveyInfo = new SurveyInfo()
  .addQuestionAndAnswer("age", "22")
  .addQuestionAndAnswer("married", "Yes")
  .addQuestionAndAnswer("citizenship", "Norwegian");
customer.setSurveyInfo(surveyInfo);
customerRepository.save(customer);

运行上面的操作会导致以下 JSON 文档存储在 MongoDB 中:

例子 186. MongoDB 中存储的 JSON 文档的例子

{ "_id" : ObjectId( "4d9e8b6e3c55287f87d4b79e" ),
  "_entity_id" : 1,
  "_entity_class" : "org.springframework.data.mongodb.examples.custsvc.domain.Customer",
  "_entity_field_name" : "surveyInfo",
  "questionsAndAnswers" : { "married" : "Yes",
    "age" : "22",
    "citizenship" : "Norwegian" },
  "_entity_field_class" : "org.springframework.data.mongodb.examples.custsvc.domain.SurveyInfo" }

20. JMX 支持

对 MongoDB 的 JMX 支持公开了在单个 MongoDB 服务器实例的 Management 数据库上执行“ serverStatus”命令的结果。它还公开了一个 ManagementMBean MongoAdmin,使您可以执行 Management 操作,例如删除或创建数据库。 JMX 功能基于 Spring 框架中可用的 JMX 功能集。有关更多详细信息,请参见here

20.1. MongoDB JMX 配置

Spring 的 Mongo 名称空间使您能够启用 JMX 功能,如以下示例所示:

例子 187. XML 模式来配置 MongoDB

<?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 id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" p:port="1099" />

    <!-- Expose JMX over RMI -->
    <bean id="serverConnector" 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 的以下屏幕截图显示了结果配置:

jconsole

21. MongoDB 3.0 支持

当连接到运行 MMap.v1 的 MongoDB 2.6/3.0 服务器或使用 MMap.v1 或 WiredTiger 存储引擎的 MongoDB 服务器 3.0 时,Spring Data MongoDB 需要 3 代 MongoDB Java 驱动程序。

Note

有关这些引擎之间的主要区别,请参阅特定于驱动程序和数据库的文档。

Note

使用 3.x MongoDB Java 驱动程序时不再有效的操作已在 Spring Data 中弃用,并将在后续发行版中删除。

21 .1.将 Spring Data MongoDB 与 MongoDB 3.0 结合使用

本节的其余部分描述了如何在 MongoDB 3.0 中使用 Spring Data MongoDB。

21.1.1. 配置选项

mongo-java-driver的某些配置选项已更改或删除。使用第 3 代驱动程序时,以下选项将被忽略:

  • autoConnectRetry

  • maxAutoConnectRetryTime

  • slaveOk

通常,在进行基于 XML 的配置时,应该使用<mongo:mongo-client … /><mongo:client-options … />而不是<mongo:mongo … />元素,因为这些元素为您提供了仅对第三代 Java 驱动程序有效的属性。以下示例显示了如何配置 MongoClient 端连接:

<?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.xsd">

  <mongo:mongo-client host="127.0.0.1" port="27017">
    <mongo:client-options write-concern="NORMAL" />
  </mongo:mongo-client>

</beans>

21.1.2. WriteConcern 和 WriteConcernChecking

Spring Data MongoDB 曾将其用作默认值的WriteConcern.NONE已在 3.0 中删除。因此,在 MongoDB 3 环境中,WriteConcern默认为WriteConcern.UNACKNOWLEGED。如果启用了WriteResultChecking.EXCEPTION,则将WriteConcern更改为WriteConcern.ACKNOWLEDGED以进行写操作。否则,执行期间的错误将不会正确抛出,因为驱动程序不会引发这些错误。

21.1.3. Authentication

连接到数据库时,MongoDB Server 第三代更改了身份验证模型。因此,某些可用于身份验证的配置选项不再有效。使用MongoCredential设置凭据以提供身份验证数据时,应使用MongoClient -specific 选项,如以下示例所示:

@Configuration
public class ApplicationContextEventTestsAppConfig extends AbstractMongoConfiguration {

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

  @Override
  @Bean
  public MongoClient mongoClient() {
    return new MongoClient(singletonList(new ServerAddress("127.0.0.1", 27017)),
      singletonList(MongoCredential.createCredential("name", "db", "pwd".toCharArray())));
  }
}

为了对 XML 配置使用身份验证,可以在<mongo-client>上使用credentials属性,如以下示例所示:

<?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.xsd">

  <mongo:mongo-client credentials="user:[emailprotected]" />

</beans>

21.1.4. 服务器端验证

MongoDB 从查询版本 3.2 开始支持Schema Validation,从 3.6 版本开始支持基于 JSON 模式的验证。

本章将指出在 MongoDB 中进行验证的专业知识,以及如何应用 JSON 模式验证。

JSON 架构验证

MongoDB 3.6 允许使用 JSON 模式草稿 4(包括核心规范和验证规范)对文档进行验证和查询,但有所不同。 $jsonSchema可以在文档验证器中使用(创建集合时),该验证器强制插入或更新的文档对架构有效。它也可以用于通过find命令或$match聚合阶段查询文档。

Spring Data MongoDB 支持 MongoDB 的特定 JSON 模式实现来定义和使用模式。有关更多详细信息,请参见JSON Schema

查询表达式验证

除了JSON 模式验证之外,MongoDB 支持(从 3.2 版开始)根据查询描述的给定结构来验证文档。可以使用与定义查询相同的方式由Criteria个对象构建该结构。以下示例显示了如何创建和使用这种验证器:

Criteria queryExpression = Criteria.where("lastname").ne(null).type(2)
    .and("age").ne(null).type(16).gt(0).lte(150);

Validator validator = Validator.criteria(queryExpression);

template.createCollection(Person.class, CollectionOptions.empty().validator(validator));

Note

在查询表达式中使用的字段名称被 Map 到域类型属性名称,同时考虑了潜在的@FieldComments。

21.1.5. 杂项详细信息

本节简要列出了使用 3.0 驱动程序时要记住的其他事项:

  • 不再支持IndexOperations.resetIndexCache()

  • 任何MapReduceOptions.extraOption都会被忽略。

  • WriteResult不再保存错误信息,而是抛出Exception

  • MongoOperations.executeInSession(…)不再呼叫requestStartrequestDone

  • 索引名称的生成已成为驱动程序内部的操作。 Spring Data MongoDB 仍然使用 2.x 模式来生成名称。

  • 在第 2 代和第 3 代服务器之间以及 MMap.v1 和 WiredTiger 存储引擎之间,某些Exception消息有所不同。

Appendix

附录 A:命名空间参考

<repositories />元素

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

表 13.属性

Name Description
base-package 定义要扫描的软件包,以在自动检测模式下扩展*Repository的存储库接口(实际接口由特定的 Spring Data 模块确定)。配置包下面的所有包也将被扫描。允许使用通配符。
repository-impl-postfix 定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选。默认为Impl
query-lookup-strategy 确定用于创建查找器查询的策略。有关详细信息,请参见“ 查询查询策略”。默认为create-if-not-found
named-queries-location 定义搜索包含外部定义查询的属性文件的位置。
consider-nested-repositories 是否应考虑嵌套的存储库接口定义。默认为false

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

<populator />元素

<populator />元素允许通过 Spring 数据存储库基础结构填充数据存储。\ [2]

表 14.属性

Name Description
locations 应该在哪里找到文件以从存储库中读取对象。

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

支持的查询关键字

下表列出了 Spring Data 存储库查询派生机制通常支持的关键字。但是,请参阅 Store 特定的文档以获取受支持关键字的确切列表,因为特定 Store 可能不支持此处列出的某些关键字。

表 15.查询关键字

Logical keyword Keyword expressions
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 IsEquals,(或没有关键字)
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

附录 D:Repositories 查询返回类型

支持的查询返回类型

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

Note

地理空间类型(例如GeoResultGeoResultsGeoPage)仅适用于支持地理空间查询的数据存储。

表 16.查询返回类型

Return type Description
void 表示没有返回值。
Primitives Java primitives.
Wrapper types Java 包装器类型。
T 唯一实体。期望查询方法最多返回一个结果。如果未找到结果,则返回null。一个以上的结果触发IncorrectResultSizeDataAccessException
Iterator<T> 一个Iterator
Collection<T> A Collection
List<T> A List
Optional<T> Java 8 或 Guava Optional。期望查询方法最多返回一个结果。如果未找到结果,则返回Optional.empty()Optional.absent()。多个结果触发IncorrectResultSizeDataAccessException
Option<T> Scala 或 Vavr Option类型。语义上与前面描述的 Java 8 的Optional相同。
Stream<T> Java 8 Stream
Streamable<T> Iterable的便捷扩展,直接将方法公开以流式处理,Map 和过滤结果,将其串联等。
实现Streamable并接受Streamable构造函数或工厂方法参数的类型 公开构造函数或以Streamable作为参数的….of(…)/….valueOf(…)工厂方法的类型。有关详情,请参见返回自定义流式包装器类型
SeqListMapSet Vavr 集合类型。有关详情,请参见支持 Vavr 收藏
Future<T> A Future。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
CompletableFuture<T> Java 8 CompletableFuture。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
ListenableFuture A org.springframework.util.concurrent.ListenableFuture。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
Slice 一定大小的数据块,用于指示是否有更多可用数据。需要Pageable方法参数。
Page<T> Slice以及其他信息,例如结果总数。需要Pageable方法参数。
GeoResult<T> 具有附加信息(例如到参考位置的距离)的结果条目。
GeoResults<T> GeoResult<T>列表以及其他信息,例如到参考位置的平均距离。
GeoPage<T> PageGeoResult<T>,例如到参考位置的平均距离。
Mono<T> 使用 Reactive 存储库的 Project Reactor Mono发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Flux<T> Project Reactor Flux使用 Reactive 存储库发出零,一个或多个元素。返回Flux的查询也可以发出无限数量的元素。
Single<T> 使用 Reactive 存储库发出单个元素的 RxJava Single。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Maybe<T> 使用 Reactive 存储库的 RxJava Maybe发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Flowable<T> RxJava Flowable使用 Reactive 存储库发出零个,一个或多个元素。返回Flowable的查询也可以发出无限数量的元素。