On this page
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功能,包括:
IoC container
虽然您不需要了解 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。这是其他有用资源的列表:
manual介绍了 MongoDB,并包含指向入门指南,参考文档和教程的链接。
online shell结合在线tutorial.提供了一种与 MongoDB 实例进行交互的便捷方法
MongoDB Java 语言中心.
您可以购买多个books。
Karl Seguin 的在线图书:小 MongoDB 书。
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 的兼容性,弃用
eval
,group
和geoNear
Template API 方法。对 MongoDB 3.4 和 MongoDB 4.0 运算符的扩展 SpEL 聚合支持(请参阅投影表达式中的 Spring 表达式支持)。
Querydsl 对反应存储库的支持通过
ReactiveQuerydslPredicateExecutor
。Aggregation framework通过存储库查询方法提供支持。
使用@Transactional的声明式被动事务。
按实体删除模板 API 会在删除查询中考虑 version 属性。
现在,无法删除版本控制的实体时,存储库删除将引发
OptimisticLockingFailureException
。在两次查询之间的存储库中支持
Range<T>
。现在,通过将* offset 和 limit *传递给服务器,更改了
Reactive/MongoOperations#count
的行为,从而将范围限制在匹配数之内。Update
操作中支持数组过滤器。JSON 模式生成来自域类型。
SpEL 支持
@Indexed
中的表达式。通过
@Document
和@Query
的基于 Comments 的归类支持。现在不建议使用接受
KClass
的 Kotlin 扩展方法,而推荐使用reified
方法。Kotlin Coroutines支持。
6.2. Spring Data MongoDB 2.1 的新增功能
基于游标的聚合执行。
Distinct queries用于命令式和反应式模板 API。
通过 Reactive 模板 API 支持 Map/Reduce。
$jsonSchema support用于查询和创建集合。
更改流支持用于命令性和响应性驱动程序。
Tailable cursors用于命令性驱动程序。
MongoDB 3.6 会话支持命令式和反应式模板 API。
MongoDB 4.0Transaction支持和 MongoDB 特定的事务 Management 器实现。
存储库查询方法的默认排序规范使用
@Query(sort=…)
。findAndReplace通过命令性和 Reactive 模板 API 提供支持。
在 MongoDB 服务器 3.0 及更高版本中,在
@Indexed
和@CompoundIndex
中弃用dropDups
不再支持dropDups
。
6.3. Spring Data MongoDB 2.0 的新功能
升级到 Java 8.
Document
API 而不是DBObject
的用法。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
和$bucketAuto
和Aggregation
进行多面聚合。
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
:当前快照M1
,M2
,依此类推:里程碑RC1
,RC2
,依此类推:发布候选RELEASE
:GA 发布SR1
,SR2
等:服务版本
可以在我们的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
我们还提供特定于持久性技术的抽象,例如JpaRepository
或MongoRepository
。这些接口扩展了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 以使用JavaConfig或XML 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. 微调存储库定义
通常,您的存储库界面扩展了Repository
,CrudRepository
或PagingAndSortingRepository
。另外,如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinition
Comments 存储库接口。扩展CrudRepository
公开了一套完整的方法来操纵您的实体。如果您希望对公开的方法保持选择性,请将要公开的方法从CrudRepository
复制到域存储库中。
Note
这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。
以下示例显示如何有选择地公开 CRUD 方法(在本例中为findById
和save
):
例子 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
中间存储库接口用@NoRepositoryBean
Comments。确保将 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> { … }
MyRepository
和UserRepository
在其类型层次结构中扩展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> { … }
AmbiguousRepository
和AmbiguousUserRepository
在其类型层次结构中仅扩展Repository
和CrudRepository
。尽管在使用唯一的 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 @Entity
Comments 进行了 Comments,因此该存储库显然属于 Spring Data JPA。 UserRepository
引用User
,使用 Spring Data MongoDB 的@Document
Comments 进行 Comments。
以下不良示例显示了使用带有混合注解的域类的存储库:
例子 11.使用带有混合 Comments 的域类的存储库定义
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了同时使用 JPA 和 Spring Data MongoDB 注解的域类。它定义了两个存储库JpaPersonRepository
和MongoDBPersonRepository
。一个用于 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
(默认)组合了CREATE
和USE_DECLARED_QUERY
。它首先查找一个声明的查询,如果找不到声明的查询,它将创建一个基于名称的自定义方法查询。这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略。它允许通过方法名称快速定义查询,还可以通过根据需要引入已声明的查询来自定义调整这些查询。
8.4.2. 查询创建
内置在 Spring Data 存储库基础结构中的查询构建器机制对于在存储库实体上构建约束查询很有用。该机制从方法中剥离前缀find…By
,read…By
,query…By
,count…By
和get…By
,然后开始解析其余部分。 Introduction 子句可以包含其他表达式,例如Distinct
,以在要创建的查询上设置不同的标志。但是,第一个By
充当分隔符,以指示实际标准的开始。在最基本的级别上,您可以定义实体属性的条件,并将它们与And
和Or
串联。以下示例显示了如何创建许多查询:
例子 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);
}
解析该方法的实际结果取决于您为其创建查询的持久性存储。但是,需要注意一些一般事项:
表达式通常是属性遍历,并带有可串联的运算符。您可以将属性表达式与
AND
和OR
结合使用。您还将获得对属性表达式的支持,例如Between
,LessThan
,GreaterThan
和Like
。支持的运算符可能因数据存储而异,因此请参考参考文档的相应部分。方法解析器支持为单个属性(例如
findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性(通常为String
instance_,例如findByLastnameAndFirstnameAllIgnoreCase(…)
)设置IgnoreCase
标志。是否支持忽略大小写可能因 Store 而异,因此请参考参考文档中有关 Store 特定查询方法的相关部分。您可以通过将
OrderBy
子句附加到引用属性的查询方法并提供排序方向(Asc
或Desc
)来应用静态排序。要创建支持动态排序的查询方法,请参阅“ 特殊参数处理”。
8.4.3. 属性表达式
如上例所示,属性表达式只能引用被管实体的直接属性。在查询创建时,您已经确保已解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设Person
具有Address
和ZipCode
。在这种情况下,该方法将创建属性遍历x.address.zipCode
。解析算法首先将整个部分(AddressZipCode
)解释为属性,然后在域类中检查具有该名称的属性(未大写)。如果算法成功,它将使用该属性。如果不是,该算法将驼峰案例部分的源从右侧分为头和尾,并尝试找到相应的属性,在我们的示例中为AddressZip
和Code
。如果该算法找到了具有该头部的属性,则将其取为尾部,并 continue 从此处开始构建树,以刚才描述的方式将尾部向上拆分。如果第一个分割不匹配,则算法将分割点移到左侧(Address
,ZipCode
)并 continue。
尽管这在大多数情况下应该可行,但算法可能会选择错误的属性。假设Person
类也具有addressZip
属性。该算法将在第一轮拆分中已经匹配,选择错误的属性,然后失败(因为addressZip
的类型可能没有code
属性)。
要解决这种歧义,您可以在方法名称中使用_
来手动定义遍历点。因此,我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准 Java 命名约定(即,在属性名称中不使用下划线,而使用驼峰大小写)。
8.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例所示。除此之外,基础架构还可以识别某些特定类型(例如Pageable
和Sort
),以将分页和排序动态应用于您的查询。下面的示例演示了这些功能:
例子 14.在查询方法中使用Pageable
,Slice
和Sort
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
具有Sort
和Pageable
的 API 期望将非null
的值传递到方法中。如果您不想应用任何排序或分页,请使用Sort.unsorted()
和Pageable.unpaged()
。
第一种方法使您可以将org.springframework.data.domain.Pageable
实例传递给查询方法,以将分页动态添加到静态定义的查询中。 Page
知道可用元素和页面的总数。它是通过基础结构触发计数查询来计算总数来实现的。由于这可能很昂贵(取决于使用的 Store),因此您可以返回Slice
。 Slice
仅知道下一个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. 限制查询结果
可以使用first
或top
关键字来限制查询方法的结果,这些关键字可以互换使用。可以将一个可选的数值附加到top
或first
以指定要返回的最大结果大小。如果省略该数字,则假定结果大小为 1.以下示例显示了如何限制查询大小:
例子 18.用Top
和First
限制查询的结果大小
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 Iterable
,List
,Set
。除此之外,我们还支持返回 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
/Seq
,java.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 使IDEA,Eclipse和Kotlin之类的工具供应商以通用方式提供了空安全支持,而无需硬编码对 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
。当传递给该方法的emailAddress
为null
时抛出IllegalArgumentException
。 - (3) 当执行的查询未产生结果时,返回
null
。还接受null
作为emailAddress
的值。 - (4) 当执行的查询未产生结果时,返回
Optional.empty()
。当传递给该方法的emailAddress
为null
时抛出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}Repositories
Comments 来触发存储库基础结构。有关 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) 在发布所有事件之后,我们将使用
@AfterDomainEventPublication
Comments 方法。它可以用来潜在地清除要发布的事件列表(以及其他用途)。
每次调用 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 配置类中的@EnableSpringDataWebSupport
Comments 来启用集成支持,如以下示例所示:
例子 45.启用 Spring Data Web 支持
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
@EnableSpringDataWebSupport
Comments 注册了一些我们稍后将讨论的组件。它还将在 Classpath 上检测 Spring HATEOAS,并为其注册集成组件(如果存在)。
或者,如果您使用 XML 配置,则将SpringDataWebConfiguration
或HateoasAwareSpringDataWebConfiguration
注册为 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使 Spring MVC 从请求参数或路径变量解析存储库 Management 的域类的实例。
HandlerMethodArgumentResolver实现使 Spring MVC 从请求参数解析
Pageable
和Sort
实例。
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
的实例。注册使Pageable
和Sort
作为有效的控制器方法参数,如以下示例所示:
例子 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()
方法,然后导入自定义的配置文件,而不使用@Enable
Comments。
如果您需要从请求中解析多个Pageable
或Sort
实例(例如,对于多个表),则可以使用 Spring 的@Qualifier
Comments 将一个实例与另一个实例区分开。然后,请求参数必须以${qualifier}_
为前缀。以下示例显示了生成的方法签名:
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必须填充thing1_page
和thing2_page
,依此类推。
传递给该方法的默认Pageable
等效于PageRequest.of(0, 20)
,但可以使用Pageable
参数上的@PageableDefault
Comments 进行自定义。
超媒体对分页的支持
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
可能会附加prev
和next
链接,具体取决于页面的状态。链接指向方法 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 JsonPath或XPath表达式(需要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
。前面的方法声明将尝试在给定文档中的任何位置找到firstname
。 lastname
XML 查找在传入文档的顶层执行。的 JSON 变体首先尝试使用顶级lastname
,但是如果前者未返回值,则还会尝试嵌套在user
子文档中的lastname
。这样,可以轻松缓解源文档结构的更改,而无需 Client 端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
如Projections中所述,支持嵌套投影。如果该方法返回复杂的非接口类型,则使用 Jackson ObjectMapper
Map 最终值。
对于 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
类型信息通常从方法的返回类型中解析。由于该信息不一定与域类型匹配,因此最好使用QuerydslPredicate
的root
属性。
以下示例显示了如何在方法签名中使用@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
的简单属性。
可以通过@QuerydslPredicate
的bindings
属性或通过使用 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.MongoClient
或com.mongodb.client.MongoClient
对象。使用基于 Java 的 Bean 元数据或使用基于 XML 的 Bean 元数据有两种主要方法。在以下各节中将讨论这两者。
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
层次结构中针对带有@Repository
Comments 的数据访问类的异常。 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]@database
→m0ng0%40dmin:mo_res%3Abw6%7D%2CQsdxx%[email protected]
有关更多详细信息,请参见RFC 3986 的 2.2 节。
从 MongoDB Java 驱动程序 3.7.0 开始,通过mongodb-driver-sync构件向MongoClient
提供了一个替代入口点。 com.mongodb.client.MongoClient
与com.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">
如果需要在用于创建SimpleMongoDbFactory
的com.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
类实现接口MongoOperations
。 MongoOperations
上的方法尽可能以 MongoDB 驱动程序Collection
对象上可用的方法命名,以使熟悉该驱动程序 API 的现有 MongoDB 开发人员熟悉该 API。例如,您可以找到find
,findAndModify
,findAndReplace
,findOne
,insert
,remove
,save
,update
和updateMulti
之类的方法。设计目标是使在基本 MongoDB 驱动程序和MongoOperations
的使用之间的转换尽可能容易。两种 API 之间的主要区别在于,可以将MongoOperations
而不是Document
传递给域对象。同样,MongoOperations
具有针对Query
,Criteria
和Update
操作的流畅的 API,而不是填充Document
为这些操作指定参数。
Note
引用MongoTemplate
实例上的操作的首选方法是通过其接口MongoOperations
。
MongoTemplate
使用的默认转换器实现是MappingMongoConverter
。 MappingMongoConverter
可以使用其他元数据来指定对象到文档的 Map,但是它也可以通过使用一些 ID 和集合名称的 Map 约定来转换不包含其他元数据的对象。这些约定以及 MapComments 的使用在“ Mapping”一章中进行了说明。
MongoTemplate
的另一个主要功能是将 MongoDB Java 驱动程序引发的异常转换为 Spring 的可移植数据访问异常层次结构。有关更多信息,请参见“ Exception Translation”。
MongoTemplate
提供了许多便利的方法来帮助您轻松执行常见任务。但是,如果您需要直接访问 MongoDB 驱动程序 API,则可以使用几种Execute
回调方法之一。 execute 回调为您提供了对com.mongodb.client.MongoCollection
或com.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
时可能要设置的其他可选属性是默认的WriteResultCheckingPolicy
,WriteConcern
和ReadPreference
属性。
Note
引用MongoTemplate
实例上的操作的首选方法是通过其接口MongoOperations
。
10.4.2. WriteResultChecking 策略
在开发中,如果从任何 MongoDB 操作返回的com.mongodb.WriteResult
包含错误,则可以方便地记录或引发异常。通常很容易忘记在开发过程中执行此操作,然后最终得到一个看起来运行成功的应用程序,而实际上该数据库并未根据您的期望进行修改。您可以将MongoTemplate
的WriteResultChecking
属性设置为以下值之一:EXCEPTION
或NONE
,分别抛出Exception
或不执行任何操作。默认值为NONE
的WriteResultChecking
值。
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
,操作(REMOVE
,UPDATE
,INSERT
,INSERT_LIST
或SAVE
)以及其他一些上下文信息。下面的示例显示了两组具有不同的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
MongoOperations
是MongoTemplate
实现的接口。
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
属性名称,导致在数据库中存储的String
和ObjectId
之间进行隐式转换。
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 类中声明为String
的id
属性或字段转换为ObjectId
并存储为ObjectId
。有效的转换规则委托给 MongoDB Java 驱动程序。如果不能将其转换为ObjectId
,那么该值将作为字符串存储在数据库中。使用 Spring
Converter<BigInteger, ObjectId>
将 Java 类中声明为BigInteger
的id
属性或字段转换为ObjectId
并存储为ObjectId
。
如果 Java 类中不存在先前规则集中指定的字段或属性,则驱动程序将生成一个隐式_id
文件,但不会将其 Map 到 Java 类的属性或字段。
在查询和更新时,MongoTemplate
使用与前面的规则相对应的转换器来保存文档,以便查询中使用的字段名称和类型可以与域类中的内容匹配。
某些环境需要一种自定义方法来 MapId
值,例如未通过 Spring 数据 Map 层运行的 MongoDB 中存储的数据。文档可以包含_id
个值,这些值可以表示为ObjectId
或String
。从 Store 中将文档读回域类型就可以了。由于ObjectId
的隐式转换,通过id
查询文档可能很麻烦。因此,无法以这种方式检索文档。对于这些情况,@MongoId
提供了对实际 IDMap 尝试的更多控制。
例子 60. @MongoId
Map
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
类,并覆盖了配置自定义MongoTypeMapper
的MappingMongoConverter
的 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
属性或字段的类型必须为String
,ObjectId
或BigInteger
。
下面的示例显示如何保存文档并检索其内容:
例子 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
重载方法,这些方法采用Query
和Update
类并将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
方法可让您设置returnNew
,upsert
和remove
的选项。从前面的代码片段扩展来的示例如下:
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 的实际匹配查询。通过查询提供
sort
,fields
和collation
设置。 - (3) 附加的可选钩子,可提供默认值以外的选项,例如
upsert
。 - (4) 用于 Map 操作结果的可选投影类型。如果没有给出,则使用初始域类型。
- (5) 触发实际执行。使用
findAndReplaceValue
而不是Optional
获得可为空的结果。
Tip
请注意,替换商品一定不能拥有id
本身,因为现有Document
的id
将由 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
标识,执行给定查询,首先应用sort
,limit
和skip
选项,然后在单独的步骤中一次删除所有文档。 - (4) 从
GOT
集合中删除所有符合查询条件的文档。与\ <3>不同,不会批量删除文档,而是逐个删除文档。 - (5) 删除
GOT
集合中的前三个文档。与\ <3>不同,不会批量删除文档,而是逐个删除文档。
10.5.9. 乐观锁
@Version
Comments 提供的语法类似于 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
的先前加载的文档。由于当前version
为1
,因此操作失败并显示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. 查询文件
您可以使用Query
和Criteria
类来表达您的查询。它们具有与本地 MongoDB 运算符名称类似的方法名称,例如lt
,lte
,is
等。 Query
和Criteria
类遵循 Fluent 的 API 样式,因此您可以将多个方法条件和查询链接在一起,同时拥有易于理解的代码。为了提高可读性,静态导入使您避免使用'new'关键字来创建Query
和Criteria
实例。您还可以使用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
上的findOne
和findById
方法来检索单个文档。这些方法返回单个域对象。我们还可以查询要作为域对象列表返回的文档集合。假设我们有多个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
对象作为参数。该对象定义用于执行查询的条件和选项。通过使用具有静态工厂方法where
的Criteria
对象来指定标准,以实例化新的Criteria
对象。我们建议对org.springframework.data.mongodb.core.query.Criteria.where
和Query.query
使用静态导入,以使查询更具可读性。
该查询应返回满足指定条件的Person
个对象的列表。本节的其余部分列出了Criteria
和Query
类的方法,这些方法与 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 字段名称,同时考虑可能的@Field
Comments。 - (3) 以
List
到Object
的形式检索所有不同的值(由于未指定明确的结果类型)。
将不同的值检索到Object
的Collection
是最灵活的方法,因为它尝试确定域类型的属性值并将结果转换为所需的类型或 MapDocument
结构。
有时,当所需字段的所有值都固定为某种类型时,直接获得正确键入的Collection
更为方便,如以下示例所示:
例子 71.检索强类型的不同值
template.query(Person.class) (1)
.distinct("lastname") (2)
.as(String.class) (3)
.all(); (4)
- (1) 查询
Person
的集合。 - (2) 选择
lastname
字段的不同值。字段名称根据域类型属性声明进行 Map,并考虑了可能的@Field
Comments。 - (3) 检索到的值将转换为所需的目标类型,在这种情况下为
String
。如果存储的字段包含文档,则也可以将值 Map 到更复杂的类型。 - (4) 以
String
的List
检索所有不同的值。如果类型不能转换为所需的目标类型,则此方法抛出DataAccessException
。
10.6.4. 地理空间查询
MongoDB 通过使用诸如$near
,$within
,geoWithin
和$nearSphere
之类的运算符来支持 GeoSpatial 查询。 Criteria
类提供了特定于地理空间查询的方法。还有一些形状类(Box
,Circle
和Point
)与地理空间相关的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删除了对以前用于运行NearQuery
的geoNear
命令的支持。
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(…)
。有关更多信息,请参见NearQuery
和Distance
的JavaDoc。
geo-earear 操作返回一个GeoResults
包装器对象,该对象封装了GeoResult
个实例。包装GeoResults
可以访问所有结果的平均距离。单个GeoResult
对象携带找到的实体及其距原点的距离。
10.6.5. GeoJSON 支持
MongoDB 支持GeoJSON和简单(旧式)坐标对的地理空间数据。这些格式可用于存储和查询数据。请参阅MongoDB 有关 GeoJSON 支持的手册以了解要求和限制。
域类中的 GeoJSON 类型
域类中GeoJSON类型的用法很简单。 org.springframework.data.mongodb.core.geo
软件包包含GeoJsonPoint
,GeoJsonPolygon
等类型。这些类型扩展了现有的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
运算符运行全文查询。 TextQuery
和TextCriteria
中提供了针对全文查询的方法和操作。进行全文搜索时,请参见MongoDB reference的行为和限制。
Full-text Search
在实际使用全文搜索之前,必须正确设置搜索索引。有关如何创建索引结构的更多详细信息,请参见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
字段,仅允许luke
和han
值。属性可以 Importing 或不 Importing。使用JsonSchemaProperty
的静态导入可以使语法稍微紧凑一些,并获得诸如string(…)
之类的入口点。 - (4) 构建模式对象。使用该架构创建集合或query documents。
通过网关接口上的静态方法,已经有一些 sched 义和强类型化架构对象(JsonSchemaObject
和JsonSchemaProperty
)。但是,您可能需要构建可通过构建器 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
除非可以通过@MongoId
Comments 获得更多特定信息,否则使用可以转换为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 ,生成的description ,enum ,allOf ,anyOf ,oneOf ,not |
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
字段,仅允许luke
和han
值。属性可以 Importing 或不 Importing。使用JsonSchemaProperty
的静态导入可以使语法稍微紧凑一些,并获得诸如string(…)
之类的入口点。 - (4) 构建模式对象。使用该架构创建集合或query documents。
通过网关接口上的静态方法,已经有一些 sched 义和强类型化架构对象(JsonSchemaObject
和JsonSchemaProperty
)。但是,您可能需要构建可通过构建器 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 ,生成的description ,enum ,allOf ,anyOf ,oneOf ,not |
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 的可选部分或可为空的部分。
FluentMongoOperations
为MongoOperations
的常用方法提供了更窄的界面,并提供了更具可读性的 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()
来检索单个实体和将多个实体检索为List
或Stream
。
当使用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,例如inValues
和regex
。
考虑下面的示例,解释类型安全查询:
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) 允许查询副本从属。
在存储库级别,@Meta
Comments 提供了以声明方式添加查询选项的方法。
@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 包含三部分:
探针:带有填充字段的域对象的实际示例。
ExampleMatcher
:ExampleMatcher
包含有关如何匹配特定字段的详细信息。可以在多个示例中重复使用它。Example
:Example
由探针和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));
}
}
包含未类型化ExampleSpec
的Example
使用存储库类型及其集合名称。类型化的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.js
和reduce.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
类指定分组操作的属性。在此示例中,我们仅使用intialDocument
和reduceFunction
方法。您还可以指定键功能以及终结器作为 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 的支持基于以下关键抽象:Aggregation
,AggregationOperation
和AggregationResults
。
Aggregation
Aggregation
代表 MongoDB aggregate
操作,并保留对聚合管道指令的描述。通过调用Aggregation
类的适当的newAggregation(…)
静态工厂方法来创建聚合,该方法采用AggregateOperation
的列表和一个可选的 Importing 类。
实际的聚合操作由MongoTemplate
的aggregate
方法执行,该方法将所需的输出类作为参数。
TypedAggregation
像Aggregation
一样,TypedAggregation
包含聚合管道的指令和对 Importing 类型的引用,该引用用于将域属性 Map 到实际文档字段。
在执行时,将考虑给定的 Importing 类型,并考虑潜在的@Field
Comments 并在引用不存在的属性时引发错误,从而对字段引用进行检查。
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 |
算术聚合运算符 | abs ,add (*通过plus ),ceil ,divide ,exp ,floor ,ln ,log ,log10 ,mod ,multiply ,pow ,sqrt ,subtract (*通过minus ),trunc |
字符串聚合运算符 | concat , substr , toLower , toUpper , stcasecmp , indexOfBytes , indexOfCP , split , strLenBytes , strLenCP , substrCP , trim , ltrim , rtim |
比较聚合运算符 | eq (* via:is ),gt ,gte ,lt ,lte ,ne |
数组聚合运算符 | 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()
方法来定义它们。 BucketOperation
和BucketAutoOperation
可以基于 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");
要在存储桶中创建输出字段,存储桶操作可以使用AggregationExpression
到andOutput()
和SpEL expressions到andOutputExpression()
。
请注意,有关存储桶表达式的更多详细信息可以在 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 表达式支持
我们支持通过ProjectionOperation
和BucketOperation
类的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 集合中定义一个组。分组条件是state
和city
字段的组合,形成了组的 ID 结构。我们使用sum
运算符从分组的元素中聚合population
属性的值,并将结果保存在pop
字段中。使用
sort
操作以pop
,state
和city
字段按升序对中间结果进行排序,以使最小城市在结果的顶部,最大城市在结果的底部。请注意,对state
和city
的排序是针对组 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 结果的最简单的方法是通过@Field
Comments 指定所需的本机 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
属性为给定包下方的所有Converter
和GenericConverter
实现启用 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 的索引。
您可以使用IndexDefinition
,GeoSpatialIndex
和TextIndexDefinition
类来创建标准索引,地理空间索引和文本索引。例如,给定上一节中定义的Venue
类,您可以声明地理空间查询,如以下示例所示:
mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location"));
Note
Index
和GeospatialIndex
支持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)
:使用给定的可空 MongoDBReadPreference
运行 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
insert
,insertList
和save
操作。onBeforeSave
:在将Document
插入或保存在数据库中之前,请在MongoTemplate
insert
,insertList
和save
操作中调用。onAfterSave
:在 将Document
插入或保存在数据库中后 在MongoTemplate
insert
,insertList
和save
操作中调用。onAfterLoad
:从数据库中检索到Document
后,在MongoTemplate
find
,findAndRemove
,findOne
和getCollection
方法中调用。onAfterConvert
:从数据库中检索到Document
之后,在MongoTemplate
find
,findAndRemove
,findOne
和getCollection
方法中调用了该方法,将其转换为 POJO。
Note
仅针对根级别类型发出生命周期事件。除非它们是用@DBRef
Comments 的文档引用,否则用作文档根目录中的属性的复杂类型将不受事件发布的约束。
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
从@Order
Comments 中接收其命令。 - (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 DataAccessResourceFailureException
和MongoException
错误代码 1003、12001、12010、12011 和 12012 到InvalidDataAccessApiUsageException
。查看实现以获取有关 Map 的更多详细信息。
10.17. 执行回调
所有 Spring 模板类的共同设计 Feature 是所有功能都路由到模板的 execute 回调方法之一中。这样做有助于确保异常和可能需要的任何资源 Management 得到一致执行。尽管 JDBC 和 JMS 比 MongoDB 更加需要此功能,但它仍为发生异常转换和日志记录提供了一个场所。因此,使用这些 execute 回调是访问 MongoDB 驱动程序的MongoDatabase
和MongoCollection
对象以执行未作为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
,以使您与文件系统进行交互。您可以将MongoDbFactory
和MongoConverter
递给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
,文件名和(可选)有关要存储的文件的元数据信息。元数据可以是任意对象,由配置为GridFsTemplate
的MongoConverter
编组。或者,您也可以提供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 存储库通过使用@Tailable
Comments 查询方法来支持无限流。这适用于返回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 端会话内的操作与会话外的操作不是隔离的。
MongoOperations
和ReactiveMongoOperations
都提供了将ClientSession
绑定到操作的网关方法。 MongoCollection
和MongoDatabase
使用实现 MongoDB 的集合和数据库接口的会话代理对象,因此您无需在每次调用时添加会话。这意味着对MongoCollection#find()
的潜在调用被委派给MongoCollection#find(ClientSession)
。
Note
诸如(Reactive)MongoOperations#getCollection
之类的方法返回本机 MongoDB Java 驱动程序网关对象(例如MongoCollection
),它们本身为ClientSession
提供专用方法。这些方法不是会话会话代理。直接与MongoCollection
或MongoDatabase
进行交互时,而不是通过MongoOperations
上的#execute
回调之一,应在需要的地方提供ClientSession
。
11.1. 同步 ClientSession 支持。
以下示例显示了会话的用法:
例子 119. ClientSession
和MongoOperations
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 功能。 MongoTransactionManager
将ClientSession
绑定到线程。 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 功能。 ReactiveMongoTransactionManager
将ClientSession
绑定到订户Context
。 ReactiveMongoTemplate
检测会话并相应地使用与事务关联的这些资源。 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 的
Query
,Criteria
和Update
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
层次结构中针对带有@Repository
Comments 的数据访问类的异常。 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。例如,您可以找到find
,findAndModify
,findOne
,insert
,remove
,save
,update
和updateMulti
之类的方法。设计目标是使在基本 MongoDB 驱动程序和ReactiveMongoOperations
的使用之间的转换尽可能容易。两种 API 之间的主要区别是ReactiveMongoOperations
可以传递域对象而不是Document
,并且 Fluent 的 api 用于Query
,Criteria
和Update
操作,而不是填充Document
来指定这些操作的参数。
Note
引用ReactiveMongoTemplate
实例上的操作的首选方法是通过其ReactiveMongoOperations
接口。
ReactiveMongoTemplate
使用的默认转换器实现是MappingMongoConverter
。 MappingMongoConverter
可以使用其他元数据来指定对象到文档的 Map,但是它也可以通过使用一些 ID 和集合名称的 Map 约定来转换不包含其他元数据的对象。这些约定以及 MapComments 的用法在Mapping chapter中进行了说明。
ReactiveMongoTemplate
的另一个主要功能是将 MongoDB Java 驱动程序中引发的异常转换为 Spring 的可移植数据访问异常层次结构。有关更多信息,请参见exception translation部分。
ReactiveMongoTemplate
上有许多便捷方法可以帮助您轻松执行常见任务。但是,如果您需要直接访问 MongoDB 驱动程序 API 来访问未由 MongoTemplate 显式公开的功能,则可以使用几种execute
回调方法之一来访问基础驱动程序 API。 execute
回调为您提供对com.mongodb.reactivestreams.client.MongoCollection
或com.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
属性设置为具有以下值LOG
,EXCEPTION
或NONE
的枚举,以记录错误,引发异常和不执行任何操作。默认值为NONE
的WriteResultChecking
值。
13.3.3. WriteConcern
如果尚未通过更高级别的驱动程序(例如MongoDatabase
)指定它,则可以设置ReactiveMongoTemplate
用于写操作的com.mongodb.WriteConcern
属性。如果未设置 ReactiveMongoTemplate 的WriteConcern
属性,则默认为 MongoDB 驱动程序的MongoDatabase
或MongoCollection
设置中的一个。
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
枚举(REMOVE
,UPDATE
,INSERT
,INSERT_LIST
和SAVE
之一)中的值的操作。信息。以下示例显示了如何创建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();
}
}
前面的示例包括存储在数据库中的String
和ObjectId
之间的隐式转换(通过使用MongoConverter
),并识别属性Id
名称的约定。
Note
前面的示例旨在显示对ReactiveMongoTemplate
的保存,更新和删除操作的使用,而不显示复杂的 Map 或链接功能。
“ Querying Documents”更详细地说明了前面示例中使用的查询语法。其他文档可在阻塞的 MongoTemplate部分中找到。
13.5. 执行回调
所有 Spring 模板类的共同设计 Feature 是所有功能都路由到其中一个模板执行回调方法中。这有助于确保异常和可能需要的所有资源 Management 的执行一致性。尽管在 JDBC 和 JMS 中这比在 MongoDB 中有更大的需要,但它仍然为异常转换和日志记录提供了一个唯一的起点。因此,使用 execute 回调是访问 MongoDB 驱动程序的MongoDatabase
和MongoCollection
对象以执行未在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
,以使您与文件系统进行交互。您可以将ReactiveMongoDatabaseFactory
和MongoConverter
递给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>
,文件名和(可选)有关要存储的文件的元数据信息。元数据可以是任意对象,由配置为ReactiveGridFsTemplate
的MongoConverter
编组。或者,您也可以提供Document
。
Note
MongoDB 的驱动程序使用AsyncInputStream
和AsyncOutputStream
接口交换二进制流。 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
的属性,类型为ObjectId
。 MongoTemplate
(支持存储库支持)中使用的默认序列化机制将名为id
的属性视为文档 ID。目前,我们支持String
,ObjectId
和BigInteger
作为 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。默认情况下,存储库会连接一个名为mongoTemplate
的MongoTemplate
Spring bean 连线,因此,如果您偏离此约定,则仅需要显式配置mongo-template-ref
。
如果您希望使用基于 Java 的配置,请使用@EnableMongoRepositories
Comments。该 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
方法显示一个查询所有具有给定姓氏的人。通过解析方法名称以获取可以与And
和Or
串联的约束来导出查询。因此,方法名称导致查询表达式{"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…]}} |
IsNotNull ,NotNull |
findByFirstnameNotNull() |
{"firstname" : {"$ne" : null}} |
IsNull ,Null |
findByFirstnameNull() |
{"firstname" : null} |
Like ,StartingWith ,EndingWith |
findByFirstnameLike(String name) |
{"firstname" : name} (name as regex) |
NotLike ,IsNotLike |
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]}}} |
IsTrue ,True |
findByActiveIsTrue() |
{"active" : true} |
IsFalse ,False |
findByActiveIsFalse() |
{"active" : false} |
Exists |
findByLocationExists(boolean exists) |
{"location" : {"$exists" : exists }} |
Note
如果属性标准比较文档,则字段 Sequences 和文档中的完全相等很重要。
14.3.1. Repositories 删除查询
上表中的关键字可以与delete…By
或remove…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.将Distance
和Metrics
一起使用
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}}
将Distance
与Metric
一起使用会导致添加$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
对象的firstname
,lastname
和Id
属性。未设置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) 通过
Query
Comments 进行静态排序。如sort
属性中所述应用排序参数。 - (4) 通过
Query
Comments 的默认排序与通过方法参数的动态 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
标准添加。一旦实体包含带有@TextScore
Comments 的属性,就可以检索文档的全文评分。此外,带 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
投影接口中的访问器方法也可以通过使用@Value
Comments 来计算新值,如以下示例所示:
例子 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 表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数。方法参数可通过名为args
的Object
数组获得。下面的示例演示如何从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提供@Value
Comments(不要与前面的界面示例中显示的 Spring 的@Value
Comments 混淆)。如果您使用 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
属性是针对方法返回类型PersonAggregate
Map 的,该方法将Sort.by("lastname")
转换为{ $sort : { '_id', 1 } }
,因为PersonAggregate.lastname
用@Id
Comments。 - (3) 将
property
的给定值替换?0
以用于动态聚合管道。 - (4)
$skip
,$limit
和$sort
可以通过Pageable
参数传递。与\ <2>中相同,运算符被附加到管道定义中。 - (5) 将返回单个
Document
的聚合结果 Map 到所需SumValue
目标类型的实例。 - (6) 导致单个文档仅包含诸如
$sum
可以直接从结果Document
中提取。为了获得更多控制权,您可以将AggregationResult
作为方法返回类型,如\ <7>所示。 - (7) 获取 Map 到通用目标包装器类型
SumValue
或org.bson.Document
的原始AggregationResults
。 - (8) 像\ <6>一样,可以直接从多个结果
Document
s 获得单个值。
Tip
您也可以将@Aggregation
与Reactive 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. 反应成分库
反应空间提供了各种反应成分库。最常见的库是RxJava和Project Reactor。
Spring Data MongoDB 构建在MongoDB 反应流驱动程序的基础上,通过依赖Reactive Streams主动性来提供最大的互操作性。静态 API(例如ReactiveMongoOperations
)是使用 Project Reactor 的Flux
和Mono
类型提供的。 Project Reactor 提供了各种适配器来转换 Reactive 包装器类型(从Flux
到Observable
,反之亦然),但是转换很容易使您的代码混乱。
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。目前,我们支持String
,ObjectId
和BigInteger
作为 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
的所有人的查询。通过解析方法名称以获取可以与And
和Or
串联的约束来导出查询。因此,方法名称导致查询表达式{"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.将Distance
和Metrics
一起使用
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>
结果。不支持GeoPage
和GeoResults
,因为它们与延迟结果方法与预先计算平均距离相矛盾。但是,您仍然可以自己传递Pageable
参数来页面结果。
将Distance
与Metric
一起使用会导致添加$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 Date
和Calendar
,JDK8 日期和时间类型以及long
或Long
的属性。
16.1.2. 基于接口的审核元数据
如果您不想使用 Comments 来定义审核元数据,则可以让您的域类实现Auditable
接口。它公开了所有审核属性的设置器方法。
还有一个方便的 Base ClassAbstractAuditable
,您可以对其进行扩展,以避免需要手动实现接口方法。这样做会增加您的域类与 Spring Data 的耦合,这可能是您要避免的事情。通常,首选基于 Comments 的方式来定义审计元数据,因为它侵入性较小且更灵活。
16.1.3. AuditorAware
如果您使用@CreatedBy
或@LastModifiedBy
,则审计基础结构需要以某种方式了解当前的主体。为此,我们提供了一个AuditorAware<T>
SPI 接口,您必须实现该接口来告知基础结构与应用程序交互的当前用户或系统是谁。通用类型T
定义必须使用@CreatedBy
或@LastModifiedBy
Comments 的属性的类型。
以下示例显示了使用 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 开始,可以通过使用@EnableMongoAuditing
Comments 对配置类进行 Comments 来启用审计,如以下示例所示:
例子 168.使用 JavaConfig 激活审计
@Configuration
@EnableMongoAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}
如果将AuditorAware
类型的 bean 暴露给ApplicationContext
,则审计基础结构会自动选择它并使用它来确定要在域类型上设置的当前用户。如果在ApplicationContext
中注册了多个实现,则可以通过显式设置@EnableMongoAuditing
的auditorAwareRef
属性来选择要使用的实现。
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 自动尝试检测要用于实现该类型对象的持久性实体的构造函数。解析算法的工作原理如下:
如果有一个无参数的构造函数,则将使用它。其他构造函数将被忽略。
如果只有一个构造函数接受参数,则将使用它。
如果有多个构造函数采用参数,则必须由
@PersistenceConstructor
CommentsSpring Data 要使用的一个。
值解析假定构造函数参数名称与实体的属性名称匹配,即,解析将像要填充该属性一样执行,包括 Map 中的所有自定义项(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或构造器上存在的@ConstructorProperties
Comments。
可以使用特定于 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)
firstname
和lastname
属性是可能通过 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 类,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下data
类Person
:
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 中,所有类默认都是不可变的,并且需要显式的属性声明来定义可变属性。考虑以下data
类Person
:
data class Person(val id: String, val name: String)
该类实际上是不可变的。当 Kotlin 生成copy(…)
方法时,它可以创建新实例,该方法创建新对象实例,该对象实例从现有对象复制所有属性值,并将作为参数提供的属性值应用于该方法。
17.2. 基于约定的 Map
MappingMongoConverter
有一些约定,用于在不提供其他 Map 元数据的情况下将对象 Map 到文档。约定是:
简短的 Java 类名称以以下方式 Map 到集合名称。类别
com.bigbank.SavingsAccount
Map 到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
,可以通过@Field
Comments 进行自定义。
表 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 字段中。如果该字段用@Id
Comments,则这也适用。如果在 Java 类中用
@MongoId
Comments 字段,则将使用其实际类型将其转换并存储为该字段。除非@MongoId
声明所需的字段类型,否则不会进行进一步的转换。如果在 Java 类中用
@MongoId(FieldType.…)
Comments 字段,则将尝试将该值转换为声明的FieldType.
如果在 Java 类中未将名为
id
id 的字段声明为 String,BigInteger 或 ObjectID,则应在应用程序中为其分配一个值,以便可以按原样存储在文档的_id 字段中。如果 Java 类中没有名为
id
的字段,则驱动程序将生成一个隐式_id
文件,但不会 Map 到 Java 类的属性或字段。
查询和更新MongoTemplate
时,将使用转换器来处理与上述保存文档规则相对应的Query
和Update
对象的转换,因此查询中使用的字段名称和类型将能够与您的域类中的内容匹配。
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} |
|
long ,Long |
本机 64-bit integer |
{"height" : 42} |
Date ,Timestamp |
本地 | {"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")} |
数组,List ,BasicDBList |
本机 | {"cookies" : [ … ]} |
boolean ,Boolean |
本地 | {"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 |
converterString |
{"value" : "741" } |
BigDecimal |
converterString |
{"value" : "741.99" } |
URL |
converter | {"website" : "https://projects.spring.io/spring-data-mongodb/" } |
Locale |
converter | {"locale : "en_US" } |
char ,Character |
转换器 | {"char" : "a" } |
NamedMongoScript |
converterCode |
{"_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")} |
LocalDateTime ,LocalTime ,Instant (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(…)
的方法,您可以重写该方法以告诉转换器在何处扫描带有@Document
Comments 的类。
您可以通过覆盖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.Document
CommentsComments 的类。
17.5. 基于元数据的 Map
要充分利用 Spring Data MongoDB 支持内的对象 Map 功能,您应该使用@Document
Comments 对 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(…)
,从而加快搜索速度。自动索引创建仅对带有@Document
Comments 的类型完成。
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 子系统通过使用@PersistenceConstructor
Comments 对构造函数进行 Comments,从而允许自定义对象构造。通过以下方式解析用于构造函数参数的值:
如果使用
@Value
Comments 对参数进行 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
参数的@Value
Comments 中的 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 也可以延迟解决。在这种情况下,对属性的实际Object
或Collection
引用将在首次访问属性时解析。使用@DBRef
的lazy
属性进行指定。还定义为延迟加载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 提供了以下扩展:
归一化的泛型支持
MongoOperations
,ReactiveMongoOperations
,FluentMongoOperations
,ReactiveFluentMongoOperations
和Criteria
。Coroutinesextensions
ReactiveFluentMongoOperations
。
18.5. Coroutines
Kotlin Coroutines是轻量级线程,允许强制性地编写非阻塞代码。在语言方面,suspend
函数提供了异步操作的抽象,而在库方面kotlinx.coroutines提供了诸如async { }的函数和Flow的类型。
Spring Data 模块在以下范围提供对协程的支持:
18.5.1. Dependencies
当kotlinx-coroutines-core
和kotlinx-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(): T
或suspend 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 中保留的字段应使用@RelatedDocument
Comments 进行 Comments。这实际上就是您需要做的。跨 Store 方面负责其余的工作,包括:
用
@Transient
标记该字段,以便 JPA 不会保留该字段跟踪对字段值所做的任何更改,并在成功完成事务后将其写入数据库
首次在应用程序中使用该值时,从 MongoDB 加载文档。
以下示例显示了一个实体,该实体的字段带有@RelatedDocument
Comments:
例子 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 的以下屏幕截图显示了结果配置:
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 到域类型属性名称,同时考虑了潜在的@Field
Comments。
21.1.5. 杂项详细信息
本节简要列出了使用 3.0 驱动程序时要记住的其他事项:
不再支持
IndexOperations.resetIndexCache()
。任何
MapReduceOptions.extraOption
都会被忽略。WriteResult
不再保存错误信息,而是抛出Exception
。MongoOperations.executeInSession(…)
不再呼叫requestStart
和requestDone
。索引名称的生成已成为驱动程序内部的操作。 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 |
Is ,Equals ,(或没有关键字) |
IS_EMPTY |
IsEmpty , Empty |
IS_NOT_EMPTY |
IsNotEmpty , NotEmpty |
IS_NOT_NULL |
NotNull , IsNotNull |
IS_NULL |
Null , IsNull |
LESS_THAN |
LessThan , IsLessThan |
LESS_THAN_EQUAL |
LessThanEqual , IsLessThanEqual |
LIKE |
Like , IsLike |
NEAR |
Near , IsNear |
NOT |
Not , IsNot |
NOT_IN |
NotIn , IsNotIn |
NOT_LIKE |
NotLike , IsNotLike |
REGEX |
Regex , MatchesRegex , Matches |
STARTING_WITH |
StartingWith , IsStartingWith , StartsWith |
TRUE |
True , IsTrue |
WITHIN |
Within , IsWithin |
附录 D:Repositories 查询返回类型
支持的查询返回类型
下表列出了 Spring Data 存储库通常支持的返回类型。但是,请参阅 Store 特定的文档以获取受支持的返回类型的确切列表,因为特定 Store 可能不支持此处列出的某些类型。
Note
地理空间类型(例如GeoResult
,GeoResults
和GeoPage
)仅适用于支持地理空间查询的数据存储。
表 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(…) 工厂方法的类型。有关详情,请参见返回自定义流式包装器类型。 |
值Seq ,List ,Map ,Set |
Vavr 集合类型。有关详情,请参见支持 Vavr 收藏。 |
Future<T> |
A Future 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
CompletableFuture<T> |
Java 8 CompletableFuture 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
ListenableFuture |
A org.springframework.util.concurrent.ListenableFuture 。期望使用@Async Comments 方法,并且需要启用 Spring 的异步方法执行功能。 |
Slice |
一定大小的数据块,用于指示是否有更多可用数据。需要Pageable 方法参数。 |
Page<T> |
Slice 以及其他信息,例如结果总数。需要Pageable 方法参数。 |
GeoResult<T> |
具有附加信息(例如到参考位置的距离)的结果条目。 |
GeoResults<T> |
GeoResult<T> 列表以及其他信息,例如到参考位置的平均距离。 |
GeoPage<T> |
Page 和GeoResult<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 的查询也可以发出无限数量的元素。 |