©2008-2018原作者。
| 本文档的副本可供您自己使用并分发给他人,前提是您不对此类副本收取任何费用,并且每份副本均包含本版权声明,无论是以印刷版还是电子版分发。 | 
- 前言
 - 参考文档
- 9.简介
 - 10. MongoDB支持
 - 11. MongoDB Repositories
 - 12.审计
 - 13.制图
 - 14.跨店支持
 - 15.记录支持
 - 16. JMX支持
 - 17. MongoDB 3.0支持
 
 - 附录
 
前言
Spring Data MongoDB项目将核心Spring概念应用于开发使用MongoDB文档样式数据存储的解决方案。我们提供了一个“模板”作为存储和查询文档的高级抽象。您可能会注意到Spring Framework提供的JDBC支持的相似之处。
本文档是Spring Data - MongoDB支持的参考指南。它解释了各种商店命名空间的MongoDB模块概念和语义以及语法。
本节提供对Spring和文档数据库的一些基本介绍。本文档的其余部分仅涉及Spring Data MongoDB功能,并假设用户熟悉MongoDB和Spring概念。
1.学习Spring
Spring Data使用Spring框架的核心功能,包括:
虽然您不需要了解Spring API,但了解它们背后的概念非常重要。至少,控制反转(IoC)背后的想法应该是熟悉的,您应该熟悉您选择使用的任何IoC容器。
可以直接使用MongoDB支持的核心功能,无需调用Spring容器的IoC服务。这很像JdbcTemplate,可以在没有Spring容器的任何其他服务的情况下使用“'standalone'”。要利用Spring Data MongoDB的所有功能,例如存储库支持,您需要配置库的某些部分以使用Spring。
要了解有关Spring的更多信息,请参阅详细说明Spring Framework的综合文档。有很多关于这个主题的文章,博客文章和书籍。有关更多信息,请参见Spring框架主页。
2.学习NoSQL和文档数据库
NoSQL商店风靡了存储世界。这是一个涉及众多解决方案,术语和模式的庞大领域(更糟糕的是,甚至术语本身也有多重含义)。虽然一些原则很常见,但您必须在某种程度上熟悉MongoDB。熟悉的最好方法是阅读文档并按照示例进行操作。它通常不需要超过5-10分钟来完成它们,特别是如果你来自RDMBS背景,这些练习可以让人大开眼界。
了解MongoDB的起点是www.mongodb.org。以下是其他有用资源的列表:
3.要求
Spring Data MongoDB 2.x二进制文件需要JDK级别8.0及以上版本和Spring Framework 4.3.20.RELEASE及以上版本。
就文档存储而言,至少需要2.6版本的MongoDB。
4.其他帮助资源
学习新框架并不总是直截了当的。在本节中,我们尝试提供我们认为易于遵循的指南,以便从Spring Data MongoDB模块开始。但是,如果您遇到问题或需要建议,请随意使用以下链接之一:
- 社区论坛
 - 
Stack Overflow上的Spring Data 是所有Spring Data(不仅仅是Document)用户共享信息和互相帮助的标记。请注意,只有发布才需要注册。
 
- 专业支持
 - 
专业的,源头支持,保证响应时间,可从Pivotal Sofware,Inc。,Spring Data和Spring后面的公司获得。
 
5.继发展之后
有关Spring Data Mongo源代码存储库,夜间构建和快照工件的信息,请参阅Spring Data Mongo 主页。您可以通过Stack Overflow社区与开发人员交互,帮助Spring Data最好地满足Spring社区的需求。要关注开发者活动,请在Spring Data Mongo 主页上查找邮件列表信息。如果您遇到错误或想要建议改进,请在Spring Data问题跟踪器上创建一张票。要及时了解Spring生态系统中的最新新闻和公告,请订阅Spring社区门户网站。您也可以关注Twitter上的Spring 博客或项目团队(SpringData)。
6.新的和值得注意的
6.1.Spring Data MongoDB 1.10中有什么新功能
- 
兼容MongoDB Server 3.4和MongoDB Java Driver 3.4。
 - 
@CountQuery,@DeleteQuery和@ExistsQuery的新注释。 - 
对MongoDB 3.2和MongoDB 3.4聚合运算符的扩展支持(请参阅支持的聚合操作)。
 - 
创建索引时支持部分过滤器表达式。
 - 
在加载或转换
DBRef实例时发布生命周期事件。 - 
为Query By Example添加了任意匹配模式。
 - 
支持
$caseSensitive和$diacriticSensitive文本搜索。 - 
支持带孔的GeoJSON Polygon。
 - 
通过批量获取
DBRef实例来提高性能。 - 
使用
$facet,$bucket和$bucketAuto以及Aggregation的多方面聚合。 
6.2.Spring Data MongoDB 1.9中有什么新功能
- 
已启用以下注释来构建您自己的组合注释:
@Document,@Id,@Field,@Indexed,@CompoundIndex,@GeoSpatialIndexed,@TextIndexed,@Query和@Meta。 - 
支持存储库查询方法中的预测。
 - 
支持按示例查询。
 - 
对象映射中
java.util.Currency的开箱即用支持。 - 
支持MongoDB 2.6中引入的批量操作。
 - 
升级到Querydsl 4。
 - 
断言与MongoDB 3.0和MongoDB Java Driver 3.2的兼容性(参见:MongoDB 3.0支持)。
 
6.3.Spring Data MongoDB 1.8中有什么新功能
- 
Criteria为创建$geoIntersects提供支持。 - 
支持
@Query中的SpEL个表达式。 - 
MongoMappingEvents公开发布它们的集合名称。 - 
改进了对
<mongo:mongo-client credentials="…" />的支持。 - 
改进了索引创建失败错误消息。
 
6.4.Spring Data MongoDB 1.7中有什么新功能
- 
断言与MongoDB 3.0和MongoDB Java Driver 3-beta3的兼容性(参见:MongoDB 3.0支持)。
 - 
支持JSR-310和ThreeTen后端口日期/时间类型。
 - 
允许
Stream作为查询方法返回类型(请参阅:查询方法)。 - 
QueryDslPredicateExcecutor现在支持findAll(OrderSpecifier<?>… orders)。 - 
支持使用脚本操作调用JavaScript函数。
 - 
改进对类似集合属性的
CONTAINS关键字的支持。 - 
支持
$bit,$mul和$position运营商到Update。 
7.依赖性
由于各个Spring Data模块的启动日期不同,因此大多数模块具有不同的主要版本号和次要版本号。找到兼容版本的最简单方法是依赖我们随定义的兼容版本提供的Spring Data版本Train BOM。在Maven项目中,您将在POM的<dependencyManagement />部分声明此依赖关系,如下所示:
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-releasetrain</artifactId>
      <version>Ingalls-SR16</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>
目前的发布列车版本为Ingalls-SR16。列车名称按字母顺序上升,此处列出了当前可用的列车。版本名称遵循以下模式:${name}-${release},其中release可以是以下之一:
- 
BUILD-SNAPSHOT:当前快照 - 
M1,M2,等等:里程碑 - 
RC1,RC2等等:释放候选人 - 
RELEASE:GA发布 - 
SR1,SR2等:服务发布 
可以在我们的Spring Data示例存储库中找到使用BOMs的工作示例。有了这个,您可以在<dependencies />块中声明要使用的Spring Data模块而不使用版本,如下所示:
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>
7.1.使用Spring Boot进行依赖管理
Spring Boot为您选择最新版本的Spring Data模块。如果您仍想升级到较新版本,请将属性spring-data-releasetrain.version配置为您要使用的列车名称和迭代。
8.使用Spring Data Repositories
Spring Data存储库抽象的目标是显着减少为各种持久性存储实现数据访问层所需的样板代码量。
| 
 Spring Data存储库文档和您的模块 本章解释了Spring Data repositories的核心概念和接口。本章中的信息来自Spring Data Commons模块。它使用Java Persistence API(JPA)模块的配置和代码示例。您应该将XML名称空间声明和要扩展的类型调整为您使用的特定模块的等效项。“ 命名空间参考 ”涵盖XML配置,支持存储库API的所有Spring Data模块都支持该配置。“ Repository查询关键字 ”涵盖了存储库抽象支持的查询方法关键字。有关模块特定功能的详细信息,请参阅本文档该模块的章节。  | 
8.1.核心概念
Spring Data存储库抽象中的中心接口是Repository。它将域类以及域类的ID类型作为类型参数进行管理。此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。CrudRepository为正在管理的实体类提供了复杂的CRUD功能。
CrudRepository接口public interface CrudRepository<T, ID extends Serializable>
    extends Repository<T, ID> {
    <S extends T> S save(S entity); (1)
    T findOne(ID primaryKey);       (2)
    Iterable<T> findAll();          (3)
    Long count();                   (4)
    void delete(T entity);          (5)
    boolean exists(ID primaryKey);  (6)
    // … more functionality omitted.
}
| 1 | 保存给定的实体。 | 
| 2 | 返回由给定ID标识的实体。 | 
| 3 | 返回所有实体。 | 
| 4 | 返回实体数量。 | 
| 5 | 删除给定的实体。 | 
| 6 | 指示是否存在具有给定ID的实体。 | 
我们还提供特定于持久性技术的抽象,例如JpaRepository或MongoRepository。这些接口扩展CrudRepository并公开底层持久性技术的功能,以及相当通用的持久性技术无关的接口,如CrudRepository。
 | 
在CrudRepository之上,有一个PagingAndSortingRepository抽象,它增加了额外的方法来简化对实体的分页访问:
PagingAndSortingRepository接口public interface PagingAndSortingRepository<T, ID extends Serializable>
  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(new PageRequest(1, 20));
除查询方法外,还可以使用计数和删除查询的查询派生。以下列表显示派生计数查询的接口定义:
public interface UserRepository extends CrudRepository<User, Long> {
  Long countByLastname(String lastname);
}
以下列表显示了派生删除查询的接口定义:
public interface UserRepository extends CrudRepository<User, Long> {
  Long deleteByLastname(String lastname);
  List<User> removeByLastname(String lastname);
}
8.2.查询方法
标准CRUD功能存储库通常对基础数据存储区进行查询。使用Spring Data,声明这些查询将变为一个包含四个步骤的过程:
- 
声明扩展Repository或其子接口之一的接口,并将其键入应处理的域类和ID类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … } - 
在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); } - 
设置Spring以使用JavaConfig或XML配置为这些接口创建代理实例。
- 
要使用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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans> 
在此示例中使用JPA名称空间。如果对任何其他商店使用存储库抽象,则需要将其更改为商店模块的相应名称空间声明。换句话说,你应该交换
jpa,例如mongodb。+另请注意,JavaConfig变体未显式配置包,因为默认情况下使用带注释的类的包。要自定义要扫描的包,请使用特定于数据存储的存储库
@Enable${store}Repositories- 注释的basePackage…属性之一。 - 
 - 
注入存储库实例并使用它,如以下示例所示:
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } } 
以下各节详细说明了每个步骤:
8.3.定义Repository接口
首先,定义特定于域类的存储库接口。接口必须扩展Repository并键入域类和ID类型。如果要公开该域类型的CRUD方法,请扩展CrudRepository而不是Repository。
8.3.1.微调Repository定义
通常,您的存储库接口扩展为Repository,CrudRepository或PagingAndSortingRepository。或者,如果您不想扩展Spring Data接口,还可以使用@RepositoryDefinition注释存储库接口。扩展CrudRepository公开了一整套操作实体的方法。如果您希望对要公开的方法有选择性,请将要从CrudRepository公开的方法复制到域存储库中。
| 这样做可以让您在提供的Spring Data Repositories功能之上定义自己的抽象。 | 
以下示例显示如何有选择地公开CRUD方法(在本例中为findById和save):
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
  T findOne(ID id);
  T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}
在前面的示例中,您为所有域存储库定义了一个公共基本接口,并公开了findOne(…)以及save(…)。这些方法被路由到由{4 /提供的所选商店的基本存储库实现中。 (例如,如果您使用JPA,则实现为SimpleJpaRepository),因为它们与CrudRepository中的方法签名匹配。因此,UserRepository现在可以保存用户,按ID查找单个用户,并触发查询以通过电子邮件地址查找Users。
中间存储库接口使用@NoRepositoryBean注释。确保将该注释添加到Spring Data不应在运行时创建实例的所有存储库接口。
 | 
8.3.2.使用Repositories与多个Spring Data模块
在应用程序中使用唯一的Spring Data模块会使事情变得简单,因为定义范围内的所有存储库接口都绑定到Spring Data模块。有时,应用程序需要使用多个Spring Data模块。在这种情况下,存储库定义必须区分持久性技术。当它在类路径上检测到多个存储库工厂时,Spring Data进入严格存储库配置模式。严格配置使用存储库或域类的详细信息来确定存储库定义的Spring Data模块绑定:
- 
如果存储库定义扩展了特定于模块的存储库,那么它是特定Spring Data模块的有效候选者。
 - 
如果使用特定于模块的类型注释对域类进行注释,则它是特定Spring Data模块的有效候选者。Spring Data模块接受第三方注释(例如JPA的
@Entity)或提供自己的注释(例如Spring Data MongoDB和Spring Data Elasticsearch的@Document。 
以下示例显示了使用特定于模块的接口的存储库(在本例中为JPA):
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
  …
}
interface UserRepository extends MyBaseRepository<User, Long> {
  …
}
MyRepository和UserRepository在其类型层次结构中扩展JpaRepository。它们是Spring Data JPA模块的有效候选者。
以下示例显示了使用通用接口的存储库:
interface AmbiguousRepository extends Repository<User, Long> {
 …
}
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
  …
}
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
  …
}
AmbiguousRepository和AmbiguousUserRepository在其类型层次结构中仅扩展Repository和CrudRepository。虽然这在使用唯一的Spring Data模块时非常好,但是多个模块无法区分应绑定哪些特定Spring Data这些存储库。
以下示例显示了使用带注释的域类的存储库:
interface PersonRepository extends Repository<Person, Long> {
 …
}
@Entity
public class Person {
  …
}
interface UserRepository extends Repository<User, Long> {
 …
}
@Document
public class User {
  …
}
PersonRepository引用了Person,它注释了JPA @Entity注释,因此该存储库显然属于Spring Data JPA。UserRepository引用User,使用Spring Data MongoDB的@Document注释进行注释。
以下错误示例显示了使用具有混合注释的域类的存储库:
interface JpaPersonRepository extends Repository<Person, Long> {
 …
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
 …
}
@Entity
@Document
public class Person {
  …
}
此示例显示了使用JPA和Spring Data MongoDB注释的域类。它定义了两个存储库JpaPersonRepository和MongoDBPersonRepository。一个用于JPA,另一个用于MongoDB用法。Spring Data不再能够将存储库分开,从而导致未定义的行为。
Repository类型详细信息和区分域类注释用于严格的存储库配置,以识别特定Spring Data模块的存储库候选。在同一域类型上使用多个持久性技术特定的注释是可能的,并允许跨多种持久性技术重用域类型。但是,Spring Data不再能够确定用于绑定存储库的唯一模块。
以下示例显示了基础包的注释驱动配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
8.4.定义查询方法
存储库代理有两种方法可以从方法名称派生特定于商店的查询:
- 
通过直接从方法名称派生查询。
 - 
通过使用手动定义的查询。
 
可用选项取决于实际商店。但是,必须有一个策略来决定创建实际查询的内容。下一节将介绍可用选项。
8.4.1.查询查找策略
存储库基础结构可以使用以下策略来解析查询。使用XML配置,您可以通过query-lookup-strategy属性在命名空间配置策略。对于Java配置,您可以使用Enable${store}Repositories注释的queryLookupStrategy属性。特定数据存储可能不支持某些策略。
- 
CREATE尝试根据查询方法名称构造特定于商店的查询。一般方法是从方法名称中删除一组已知的前缀,并解析方法的其余部分。您可以在“ 查询创建 ”中阅读有关查询构造的更多信息。 - 
USE_DECLARED_QUERY尝试查找声明的查询,如果找不到,则抛出异常。查询可以通过某处的注释来定义,也可以通过其他方式声明。查阅特定商店的文档以查找该商店的可用选项。如果存储库基础结构在引导时未找到该方法的声明查询,则它将失败。 - 
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连接起来。以下示例显示了如何创建大量查询:
public interface PersonRepository extends Repository<User, 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等运算符的支持。支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。 - 
方法解析器支持为各个属性设置
IgnoreCase标志(例如,findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常为String个实例 - 例如findByLastnameAndFirstnameAllIgnoreCase(…)) 。是否支持忽略案例可能因商店而异,因此请参阅参考文档中有关特定于商店的查询方法的相关章节。 - 
您可以通过将
OrderBy子句附加到引用属性的查询方法并提供排序方向(Asc或Desc)来应用静态排序。要创建支持动态排序的查询方法,请参阅“ 特殊参数处理 ”。 
8.4.3.财产表达
属性表达式只能引用被管实体的直接属性,如前面的示例所示。在创建查询时,您已确保已解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。请考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设Person的Address为ZipCode。在这种情况下,该方法创建属性遍历x.address.zipCode。解析算法首先将整个部分(AddressZipCode)解释为属性,并检查域类中是否具有该名称的属性(未大写)。如果算法成功,则使用该属性。如果没有,算法将来自右侧的驼峰案例部分的源分成头部和尾部,并尝试找到相应的属性 - 在我们的示例中,AddressZip和Code。如果算法找到具有该头部的属性,则它采用尾部并继续从那里构建树,以刚才描述的方式将尾部分开。如果第一个分割不匹配,算法会将分割点移动到左侧(Address,ZipCode)并继续。
虽然这应该适用于大多数情况,但算法可能会选择错误的属性。假设Person类也具有addressZip属性。该算法将在第一个拆分轮中匹配,选择错误的属性,并失败(因为addressZip的类型可能没有code属性)。
要解决这种歧义,您可以在方法名称中使用_来手动定义遍历点。所以我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议遵循标准Java命名约定(即,不在属性名称中使用下划线,而是使用camel case)。
8.4.4.特殊参数处理
要处理查询中的参数,请定义方法参数,如前面示例中所示。除此之外,基础结构还可以识别某些特定类型,如Pageable和Sort,以动态地对您的查询应用分页和排序。以下示例演示了这些功能:
Pageable,Slice和SortPage<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);
第一种方法允许您将org.springframework.data.domain.Pageable实例传递给查询方法,以动态地将分页添加到静态定义的查询中。Page知道可用元素和页面的总数。它通过基础设施触发计数查询来计算总数来实现。由于这可能很昂贵(取决于所使用的商店),您可以返回Slice。Slice只知道下一个Slice是否可用,这在浏览更大的结果集时可能就足够了。
排序选项也通过Pageable实例处理。如果只需要排序,请在方法中添加org.springframework.data.domain.Sort参数。如您所见,也可以返回List。在这种情况下,不会创建构建实际Page实例所需的其他元数据(反过来,这意味着不会发出必要的附加计数查询)。相反,它限制查询仅查找给定范围的实体。
| 要了解整个查询的页数,您必须触发额外的计数查询。默认情况下,此查询是从您实际触发的查询派生的。 | 
8.4.5.限制查询结果
可以使用first或top关键字来限制查询方法的结果,这些关键字可以互换使用。可以将可选的数值附加到top或first以指定要返回的最大结果大小。如果省略该数字,则假定结果大小为1。以下示例显示如何限制查询大小:
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关键字将结果包装。
如果将分页或切片应用于限制查询分页(以及可用页数的计算),则将其应用于有限结果中。
通过使用Sort参数将结果与动态排序结合使用,可以表达“K”最小元素和“K”元素的查询方法。
 | 
8.4.6.流式查询结果
可以使用Java 8 Stream<T>作为返回类型以递增方式处理查询方法的结果。而不是将查询结果包装在Stream数据存储中,特定的方法用于执行流式处理,如以下示例所示:
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);
Stream可能包装基础数据存储特定资源,因此必须在使用后关闭。您可以使用close()方法或使用Java 7 try-with-resources块手动关闭Stream,如以下示例所示:
 | 
Stream<T>导致try-with-resources块try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}
并非所有Spring Data模块当前都支持Stream<T>作为返回类型。
 | 
8.4.7.异步查询结果
Repository查询可以使用Spring的异步方法执行功能异步运行。这意味着该方法在调用时立即返回,而实际查询执行发生在已提交到Spring TaskExecutor的任务中。异步查询执行与响应式查询执行不同,不应混合使用。有关反应支持的更多详细信息,请参阅特定于商店的文档。以下示例显示了许多异步查询:
@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.创建Repository实例
在本节中,您将为定义的存储库接口创建实例和bean定义。一种方法是使用每个支持存储库机制的Spring Data模块附带的Spring命名空间,尽管我们通常建议使用Java配置。
8.5.1.XML配置
每个Spring Data模块都包含一个repositories元素,可让您定义Spring为您扫描的基础包,如以下示例所示:
<?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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://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属性允许使用通配符,以便您可以定义扫描包的模式。
使用过滤器
默认情况下,基础结构会选择扩展位于已配置的基础包下的持久性技术特定的Repository子接口的每个接口,并为其创建一个bean实例。但是,您可能希望对哪些接口为其创建bean实例进行更细粒度的控制。为此,请在<repositories />元素中使用<include-filter />和<exclude-filter />元素。语义完全等同于Spring上下文命名空间中的元素。有关详细信息,请参阅这些元素的Spring参考文档。
例如,要将某些接口从实例化中排除为存储库bean,可以使用以下配置:
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了以SomeRepository结尾的所有接口被实例化。
8.5.2.JavaConfig
还可以通过在JavaConfig类上使用特定于商店的@Enable${store}Repositories注释来触发存储库基础结构。有关Spring容器的基于Java的配置的介绍,请参阅Spring参考文档中的JavaConfig。
启用Spring Data repositories的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
  @Bean
  public EntityManagerFactory entityManagerFactory() {
    // …
  }
}
上面的示例使用JPA特定的注释,您可以根据实际使用的商店模块进行更改。这同样适用于EntityManagerFactory bean的定义。请参阅有关特定于商店的配置的部分。
 | 
8.5.3.独立使用
您还可以在Spring容器之外使用存储库基础结构 - 例如,在CDI环境中。您仍然需要在类路径中使用一些Spring库,但通常也可以通过编程方式设置存储库。提供存储库支持的Spring Data模块提供了一个特定于持久性技术的RepositoryFactory,您可以按如下方式使用它:
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
8.6.Spring Data repositories的自定义实现
通常有必要为一些存储库方法提供自定义实现。Spring Data repositories轻松允许您提供自定义存储库代码并将其与通用CRUD抽象和查询方法功能集成。
8.6.1.将自定义行为添加到单个存储库
要使用自定义功能丰富存储库,首先要为自定义功能定义接口和实现。使用您提供的存储库接口来扩展自定义接口。
interface UserRepositoryCustom {
  public void someCustomMethod(User user);
}
然后,您可以让存储库接口从片段接口进一步扩展,如以下示例所示:
class UserRepositoryImpl implements UserRepositoryCustom {
  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
找到类的最重要的位是其上的名称的Impl后缀与核心存储库接口(见下文)。
 | 
实现本身不依赖于Spring Data并且可以是常规Spring bean。因此,您可以使用标准依赖项注入行为将引用注入其他bean(例如JdbcTemplate),参与方面等。
您可以让存储库接口扩展片段接口,如以下示例所示:
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
  // Declare query methods here
}
让标准存储库接口扩展自定义存储库接口。这样做结合了CRUD和自定义功能,并使其可供客户使用。
组态
如果使用命名空间配置,则存储库基础结构会尝试通过扫描我们在存储库中找到的包下面的类来自动检测自定义实现。这些类需要遵循将命名空间元素的属性repository-impl-postfix附加到找到的存储库接口的命名约定。名称。此后缀默认为Impl。
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
第一个配置示例尝试查找类com.acme.repository.UserRepositoryImpl以充当自定义存储库实现,而第二个示例将尝试查找com.acme.repository.UserRepositoryFooBar。
手动接线
如果您的自定义实现仅使用基于注释的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他Spring bean。如果您的自定义实现bean需要特殊连接,您可以声明bean并根据前一节中描述的约定对其进行命名。然后,基础结构按名称引用手动定义的bean定义,而不是自己创建一个。以下示例显示如何手动连接自定义实现:
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
  <!-- further configuration -->
</beans:bean>
8.6.2.向所有存储库添加自定义行为
如果要向所有存储库接口添加单个方法,则上述方法不可行。要向所有存储库添加自定义行为,首先要添加一个中间接口来声明共享行为。
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
  extends PagingAndSortingRepository<T, ID> {
  void sharedCustomMethod(ID id);
}
现在,您的各个存储库接口将扩展此中间接口而不是Repository接口,以包含声明的功能。接下来,创建中间接口的实现,该实现扩展特定于持久性技术的存储库基类。然后,此类将充当存储库代理的自定义基类。
public class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
  private final EntityManager entityManager;
  public MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);
    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }
  public void sharedCustomMethod(ID id) {
    // implementation goes here
  }
}
该类需要具有特定于商店的存储库工厂实现所使用的超类的构造函数。如果存储库基类具有多个构造函数,则覆盖使用EntityInformation加上特定于商店的基础结构对象(例如EntityManager或模板类)的构造函数。
 | 
Spring <repositories />命名空间的默认行为是为base-package下的所有接口提供实现。这意味着如果保持其当前状态,Spring将创建MyRepository的实现实例。这当然不是所希望的,因为它只是作为Repository与您要为每个实体定义的实际存储库接口之间的中介。要将扩展Repository的接口排除在实例化为存储库实例之外,您可以使用@NoRepositoryBean注释它(如上所示)或将其移出已配置的base-package。
最后一步是使Spring Data基础结构了解自定义存储库基类。在Java配置中,您可以使用@Enable${store}Repositories注释的repositoryBaseClass属性来执行此操作,如以下示例所示:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML命名空间中提供了相应的属性,如以下示例所示:
<repositories base-package="com.acme.repository"
     base-class="….MyRepositoryImpl" />
8.7.从Aggregate Roots发布Events
由存储库管理的实体是聚合根。在域驱动设计应用程序中,这些聚合根通常会发布域事件。Spring Data提供了一个名为@DomainEvents的注释,您可以在聚合根的方法上使用该注释,使该发布尽可能简单,如以下示例所示:
class AnAggregateRoot {
    @DomainEvents (1)
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }
    @AfterDomainEventsPublication (2)
    void callbackMethod() {
       // … potentially clean up domain events list
    }
}
| 1 | 使用@DomainEvents的方法可以返回单个事件实例或事件集合。它不能采取任何论点。 | 
| 2 | 在所有事件发布后,我们有一个用@AfterDomainEventsPublication注释的方法。它可用于潜在地清除要发布的事件列表(以及其他用途)。 | 
每次调用Spring Data存储库的save(…)方法之一时,都会调用这些方法。
8.8.Spring Data扩展
本节介绍了一组Spring Data扩展,可在各种环境中使用Spring Data。目前,大多数整合都针对Spring MVC。
8.8.1.Querydsl扩展
Querydsl是一个框架,可以通过其流畅的API构建静态类型的类SQL查询。
几个Spring Data模块提供与Querydsl到QueryDslPredicateExecutor的集成,如以下示例所示:
public interface QueryDslPredicateExecutor<T> {
    T findOne(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,如以下示例所示
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.Web支持
| 本节包含Spring Data web支持的文档,因为它在Spring Data Commons的当前(及更高版本)版本中实现。由于新引入的支持更改了许多内容,因此我们将旧行为的文档保留在Legacy web支持中。 | 
支持存储库编程模型的Spring Data模块具有各种web支持。web相关组件需要Spring MVC JAR位于类路径上。其中一些甚至提供与Spring HATEOAS的集成。通常,通过在JavaConfig配置类中使用@EnableSpringDataWebSupport批注来启用集成支持,如以下示例所示:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }
@EnableSpringDataWebSupport注释注册了一些我们稍后会讨论的组件。它还将检测类路径上的Spring HATEOAS并为其注册集成组件(如果存在)。
或者,如果您使用XML配置,请将SpringDataWebConfiguration或HateoasAwareSpringDataWebConfiguration注册为Spring bean,如以下示例所示(对于SpringDataWebConfiguration):
<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" />
基本Web支持
上一节中显示的配置注册了一些基本组件:
- 
A
DomainClassConverter允许Spring MVC从请求参数或路径变量中解析存储库管理的域类的实例。 - 
HandlerMethodArgumentResolver允许Spring MVC从请求参数中解析Pageable和Sort实例的实现。 
DomainClassConverter
DomainClassConverter允许您直接在Spring MVC控制器方法签名中使用域类型,因此您无需通过存储库手动查找实例,如以下示例所示:
@Controller
@RequestMapping("/users")
public class UserController {
  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {
    model.addAttribute("user", user);
    return "userForm";
  }
}
如您所见,该方法直接接收User实例,无需进一步查找。可以通过让Spring MVC首先将路径变量转换为域类的id类型来解析实例,并最终通过在为域类型注册的存储库实例上调用findOne(…)来访问实例。
目前,存储库必须实现CrudRepository才有资格被发现进行转换。
 | 
HandlerMethodArgumentResolvers for Pageable和Sort
上一节中显示的配置代码段还注册了PageableHandlerMethodArgumentResolver以及SortHandlerMethodArgumentResolver的实例。注册启用Pageable和Sort作为有效的控制器方法参数,如以下示例所示:
@Controller
@RequestMapping("/users")
public class UserController {
  @Autowired UserRepository repository;
  @RequestMapping
  public String showUsers(Model model, Pageable pageable) {
    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}
上述方法签名导致Spring MVC尝试使用以下默认配置从请求参数派生Pageable实例:
  | 
您要检索的页面。0索引并默认为0。  | 
  | 
要检索的页面大小。默认为20。  | 
  | 
Properties应按  | 
要自定义此行为,请扩展SpringDataWebConfiguration或启用HATEOAS的等效项,并覆盖pageableResolver()或sortResolver()方法并导入自定义配置文件,而不是使用@Enable  - 注释。
如果您需要从请求中解析多个Pageable或Sort实例(例如,对于多个表),则可以使用Spring的@Qualifier注释来区分彼此。然后,请求参数必须以${qualifier}_为前缀。以下示例显示了生成的方法签名:
public String showUsers(Model model,
      @Qualifier("thing1") Pageable first,
      @Qualifier("thing2") Pageable second) { … }
你必须填充thing1_page和thing2_page等等。
传递给方法的默认Pageable等同于new PageRequest(0, 20),但可以使用Pageable参数上的@PageableDefault注释进行自定义。
对Pageables的超媒体支持
Spring HATEOAS附带了一个表示模型类(PagedResources),它允许使用必要的Page元数据丰富Page实例的内容以及允许客户轻松浏览页面的链接。将Page转换为PagedResources是通过Spring HATEOAS ResourceAssembler接口的实现来完成的,称为PagedResourcesAssembler。以下示例显示如何使用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个链接,具体取决于页面的状态。链接指向方法映射到的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数据绑定支持
Spring Data投影(在Projections中描述)可用于通过使用JSONPath表达式来绑定传入的请求有效负载(需要Jayway JsonPath或XPath表达式(需要XmlBeam),如以下示例所示:
@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。这样,可以轻松地减轻源文档结构的变化,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
如预测中所述,支持嵌套投影。如果该方法返回复杂的非接口类型,则使用Jackson ObjectMapper来映射最终值。
对于Spring MVC,只要@EnableSpringDataWebSupport处于活动状态,就会自动注册必要的转换器,并且类路径上可以使用所需的依赖项。要与RestTemplate一起使用,请手动注册ProjectingJackson2HttpMessageConverter(JSON)或XmlBeamHttpMessageConverter。
有关更多信息,请参阅规范Spring Data示例存储库中的web投影示例。
Querydsl Web支持
对于那些具有QueryDSL集成的商店,可以从Request查询字符串中包含的属性派生查询。
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的User对象,可以使用QuerydslPredicateArgumentResolver将查询字符串解析为以下值。
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
在类路径上找到Querydsl时,会自动启用该功能以及@EnableSpringDataWebSupport。
 | 
在方法签名中添加@QuerydslPredicate可提供即用型Predicate,可以使用QuerydslPredicateExecutor运行。@QuerydslPredicate。
通常从方法的返回类型中解析类型信息。由于该信息不一定与域类型匹配,因此使用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。 | 
默认绑定如下:
- 
关于
eq的简单属性的Object。 - 
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 public 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.Repository人口
如果使用Spring JDBC模块,您可能熟悉使用SQL脚本填充DataSource的支持。虽然它不使用SQL作为数据定义语言,但它在存储库级别上提供了类似的抽象,因为它必须与存储无关。因此,填充程序支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。
假设您有一个文件data.json,其中包含以下内容:
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]
您可以使用Spring Data Commons中提供的存储库命名空间的populator元素来填充您的存储库。要将前面的数据填充到PersonRepository,请声明类似于以下内容的populator:
<?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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd">
  <repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的声明导致data.json文件被杰克逊ObjectMapper读取和反序列化。
通过检查JSON文档的_class属性来确定解组JSON对象的类型。基础结构最终选择适当的存储库来处理反序列化的对象。
要使用XML来定义应该填充存储库的数据,可以使用unmarshaller-populator元素。您将其配置为使用Spring OXM中提供的XML marshaller选项之一。有关详细信息,请参阅Spring参考文档。以下示例说明如何使用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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd
    http://www.springframework.org/schema/oxm
    http://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>
8.8.4.遗产web支持
Spring MVC的域类web绑定
鉴于您正在开发Spring MVC web应用程序,您通常必须从URL解析域类ID。默认情况下,您的任务是将请求参数或URL部分转换为域类,以将其传递给下面的层,或者直接在实体上执行业务逻辑。这看起来像这样:
@Controller
@RequestMapping("/users")
public class UserController {
  private final UserRepository userRepository;
  @Autowired
  public UserController(UserRepository userRepository) {
    Assert.notNull(repository, "Repository must not be null!");
    this.userRepository = userRepository;
  }
  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {
    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user
    model.addAttribute("user", user);
    return "user";
  }
}
首先,为每个控制器声明一个存储库依赖关系,以分别查找由控制器或存储库管理的实体。查找实体也是样板,因为它始终是findOne(…)调用。幸运的是,Spring提供了注册自定义组件的方法,允许在String值到任意类型之间进行转换。
属性编辑器
对于3.0之前的Spring版本,必须使用简单的Java PropertyEditors。为了与它集成,Spring Data提供DomainClassPropertyEditorRegistrar,它查找ApplicationContext中注册的所有Spring Data repositories并为托管域类注册自定义PropertyEditor。
<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
      </property>
    </bean>
  </property>
</bean>
如果您已按照前面的示例配置Spring MVC,则可以按如下方式配置控制器,这样可以减少大量的混乱和样板。
@Controller
@RequestMapping("/users")
public class UserController {
  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {
    model.addAttribute("user", user);
    return "userForm";
  }
}
参考文档
9.简介
9.1.文件结构
这部分参考文档解释了Spring Data MongoDB提供的核心功能。
“ MongoDB支持 ”引入了MongoDB模块功能集。
“ MongoDB Repositories ”引入了MongoDB的存储库支持。
10. MongoDB支持
MongoDB支持包含多种功能:
- 
Spring配置支持基于Java的
@Configuration类或Mongo驱动程序实例和副本集的XML命名空间。 - 
MongoTemplate辅助类,可在执行常见的Mongo操作时提高工作效率。包括文档和POJO之间的集成对象映射。 - 
异常转换为Spring的可移植数据访问异常层次结构。
 - 
与Spring转换服务集成的功能丰富的对象映射。
 - 
基于注释的映射元数据,可扩展以支持其他元数据格式。
 - 
持久性和映射生命周期事件。
 - 
基于Java的查询,标准和更新DSL。
 - 
自动实现Repository接口,包括支持自定义查找程序方法。
 - 
QueryDSL集成以支持类型安全查询。
 - 
跨存储持久性 - 使用MongoDB透明地持久保存/检索具有字段的JPA实体的支持。
 - 
Log4j日志appender。
 - 
地理空间整合。
 
对于大多数任务,您应该使用MongoTemplate或Repository支持,它们都利用丰富的映射功能。MongoTemplate是寻找访问功能的地方,例如递增计数器或临时CRUD操作。MongoTemplate还提供了回调方法,以便您可以轻松获取低级API工件(例如com.mongo.DB)与MongoDB直接通信。关于各种API工件的命名约定的目标是复制基本MongoDB Java驱动程序中的那些,以便您可以轻松地将现有知识映射到Spring API。
10.1.入门
Spring MongoDB支持需要MongoDB 2.6或更高版本以及Java SE 6或更高版本。引导程序设置工作环境的一种简单方法是在STS中创建基于Spring的项目。
首先,您需要设置一个正在运行的MongoDB服务器。有关如何启动MongoDB实例的说明,请参阅MongoDB快速入门指南。安装后,启动MongoDB通常需要运行以下命令:${MONGO_HOME}/bin/mongod
在STS中创建Spring项目:
- 
转到文件→新建→Spring模板项目→简单Spring实用程序项目,然后在出现提示时按是。然后输入项目和包名称,例如
org.spring.mongodb.example。。将以下内容添加到pom.xml文件dependencies元素:<dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>1.10.16.RELEASE</version> </dependency> </dependencies> - 
将pom.xml中的Spring版本更改为
<spring.framework.version>4.3.20.RELEASE</spring.framework.version> - 
将Maven的Spring里程碑存储库的以下位置添加到
pom.xml,使其与<dependencies/>元素位于同一级别:<repositories> <repository> <id>spring-milestone</id> <name>Spring Maven MILESTONE Repository</name> <url>http://repo.spring.io/libs-milestone</url> </repository> </repositories> 
存储库也可以在这里浏览。
您可能还希望将日志记录级别设置为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.Mongo;
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 Mongo(), "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 DBObject 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]
即使在这个简单的例子中,也很少有人注意到:
- 
您可以
MongoTemplate使用标准com.mongodb.Mongo对象和要使用的数据库名称来实例化Spring Mongo的中央帮助程序类。 - 
映射器可以对标准POJO对象进行工作,而无需任何其他元数据(尽管您可以选择提供该信息。请参阅此处。)。
 - 
约定用于处理
id字段,在存储在数据库中时将其转换为ObjectId。 - 
映射约定可以使用字段访问。请注意,
Person类只有getter。 - 
如果构造函数参数名称与存储文档的字段名称匹配,则它们用于实例化对象
 
10.2.例子Repository
有一个GitHub存储库,其中包含几个示例,您可以下载和使用这些示例来了解库的工作原理。
10.3.使用Spring连接到MongoDB
使用MongoDB和Spring时的首要任务之一是使用IoC容器创建com.mongodb.Mongo对象。通过使用基于Java的bean元数据或使用基于XML的bean元数据,有两种主要方法可以实现此目的。两者都在以下部分中讨论。
10.3.1.使用基于Java的元数据注册Mongo实例
以下示例显示了使用基于Java的bean元数据注册com.mongodb.Mongo的实例的示例:
com.mongodb.Mongo对象@Configuration
public class AppConfig {
  /*
   * Use the standard Mongo driver API to create a com.mongodb.Mongo instance.
   */
   public @Bean Mongo mongo() throws UnknownHostException {
       return new Mongo("localhost");
   }
}
此方法允许您使用可能已经习惯使用的标准com.mongodb.Mongo API,但也会使用UnknownHostException检查异常来污染代码。由于基于Java的bean元数据使用方法作为设置对象依赖性的手段,使得调用代码混乱,因此不希望使用checked异常。
另一种方法是使用Spring的MongoClientFactoryBean向容器注册com.mongodb.Mongo实例的实例。与直接实例化com.mongodb.Mongo实例相比,FactoryBean方法不会抛出已检查的异常,并且还具有为容器提供ExceptionTranslator实现的附加优势,该实现将MongoDB异常转换为Spring中的异常用@Repository注释注释的数据访问类的可移植DataAccessException层次结构。在Spring的DAO支持功能中描述了此层次结构和@Repository的使用。
以下示例显示了基于Java的bean元数据的示例,该元数据支持@Repository带注释的类的异常转换:
com.mongodb.Mongo对象并启用Spring的异常转换支持@Configuration
public class AppConfig {
    /*
     * Factory bean that creates the com.mongodb.Mongo instance
     */
     public @Bean MongoClientFactoryBean mongo() {
          MongoClientFactoryBean mongo = new MongoClientFactoryBean();
          mongo.setHost("localhost");
          return mongo;
     }
}
要访问MongoClientFactoryBean在其他@Configuration类或您自己的类中创建的com.mongodb.Mongo对象,请使用private @Autowired Mongo mongo;字段。
10.3.2.使用基于XML的元数据注册Mongo实例
虽然您可以使用Spring的传统<beans/> XML命名空间向容器注册com.mongodb.Mongo的实例,但XML可能非常详细,因为它是通用的。XML命名空间是配置常用对象(如Mongo实例)的更好选择。mongo命名空间允许您创建Mongo实例服务器位置,副本集和选项。
要使用Mongo名称空间元素,您需要引用Mongo模式,如下所示:
<?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
          http://www.springframework.org/schema/context/spring-context.xsd
          http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- Default bean name is 'mongo' -->
    <mongo:mongo host="localhost" port="27017"/>
</beans>
以下示例显示了使用MongoOptions的更高级配置(请注意,这些不是建议值):
<beans>
  <mongo:mongo host="localhost" port="27017">
    <mongo: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/>
</beans>
以下示例显示了使用副本集的配置:
com.mongodb.Mongo对象的XML模式<mongo:mongo id="replicaSetMongo" replica-set="127.0.0.1:27017,localhost:27018"/>
10.3.3.MongoDbFactory接口
虽然com.mongodb.Mongo是MongoDB驱动程序API的入口点,但连接到特定的MongoDB数据库实例需要其他信息,例如数据库名称和可选的用户名和密码。使用该信息,您可以获取com.mongodb.DB对象并访问特定MongoDB数据库实例的所有功能。Spring提供了org.springframework.data.mongodb.core.MongoDbFactory接口,如下面的清单所示,用于引导到数据库的连接:
public interface MongoDbFactory {
  DB getDb() throws DataAccessException;
  DB getDb(String dbName) throws DataAccessException;
}
以下部分显示如何使用具有基于Java或基于XML的元数据的容器来配置MongoDbFactory接口的实例。反过来,您可以使用MongoDbFactory实例配置MongoTemplate。
类org.springframework.data.mongodb.core.SimpleMongoDbFactory提供了实现MongoDbFactory接口,并使用标准com.mongodb.Mongo实例,数据库名称和可选的org.springframework.data.authentication.UserCredentials构造函数参数创建。
您可以在标准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 Mongo(), "database"));
    mongoOps.insert(new Person("Joe", 34));
    log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));
    mongoOps.dropCollection("person");
  }
}
粗体代码突出显示SimpleMongoDbFactory的使用,并且是入门部分中显示的列表之间的唯一区别。
10.3.4.使用基于Java的元数据注册MongoDbFactory实例
要在容器中注册MongoDbFactory实例,您可以编写与上一代码清单中突出显示的代码非常相似的代码。以下清单显示了一个简单示例:
@Configuration
public class MongoConfiguration {
  public @Bean MongoDbFactory mongoDbFactory() throws Exception {
    return new SimpleMongoDbFactory(new Mongo(), "database");
  }
}
要定义用户名和密码,请创建org.springframework.data.authentication.UserCredentials的实例并将其传递给构造函数,如下所示。此列表还显示使用MongoDbFactory向容器注册MongoTemplate的实例。
@Configuration
public class MongoConfiguration {
  public @Bean MongoDbFactory mongoDbFactory() throws Exception {
    UserCredentials userCredentials = new UserCredentials("joe", "secret");
    return new SimpleMongoDbFactory(new Mongo(), "database", userCredentials);
  }
  public @Bean MongoTemplate mongoTemplate() throws Exception {
    return new MongoTemplate(mongoDbFactory());
  }
}
10.3.5.使用基于XML的元数据注册MongoDbFactory实例
与使用<beans/>命名空间相比,mongo命名空间提供了创建SimpleMongoDbFactory的便捷方法,如以下示例所示:
<mongo:db-factory dbname="database">
在上面的示例中,使用默认主机和端口号创建com.mongodb.Mongo实例。除非指定了id属性的值,否则通过id'mongoDbFactory'标识容器中注册的SimpleMongoDbFactory。
除了数据库的用户名和密码之外,您还可以为底层com.mongodb.Mongo实例提供主机和端口,如下所示。
<mongo:db-factory id="anotherMongoDbFactory"
                  host="localhost"
                  port="27017"
                  dbname="database"
                  username="joe"
                  password="secret"/>
如果MongoDB身份验证数据库与目标数据库不同,请使用authentication-dbname属性,如下所示。
<mongo:db-factory id="anotherMongoDbFactory"
                  host="localhost"
                  port="27017"
                  dbname="database"
                  username="joe"
                  password="secret"
                  authentication-dbname="admin"
                  />
如果需要在用于创建SimpleMongoDbFactory的com.mongodb.Mongo实例上配置其他选项,则可以使用mongo-ref属性引用现有bean,如以下示例所示。为了显示另一种常见的使用模式,下面的清单显示了属性占位符的使用,它允许您参数化配置和MongoTemplate的创建:
<context:property-placeholder location="classpath:/com/myapp/mongodb/config/mongo.properties"/>
<mongo:mongo host="${mongo.host}" port="${mongo.port}">
  <mongo: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>
<mongo:db-factory dbname="database" mongo-ref="mongo"/>
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
10.4.MongoTemplate简介
位于org.springframework.data.mongodb.core包中的MongoTemplate类是Spring MongoDB支持的中心类,并提供了与数据库交互的丰富功能集。该模板提供了便捷的操作来创建,更新,删除和查询MongoDB文档,并提供域对象和MongoDB文档之间的映射。
配置完成后,MongoTemplate是线程安全的,可以跨多个实例重用。
 | 
MongoDB文档和域类之间的映射是通过委托接口MongoConverter的实现来完成的。Spring提供了两种实现,SimpleMappingConverter和MappingMongoConverter,但您也可以编写自己的转换器。有关更多详细信息,请参阅MongoConverters部分。
MongoTemplate类实现接口MongoOperations。尽可能地,MongoOperations上的方法以MongoDB驱动程序Collection对象上可用的方法命名,以使API熟悉用于驱动程序API的现有MongoDB开发人员。例如,您可以找到find,findAndModify,findOne,insert,remove,save,update和updateMulti等方法。设计目标是尽可能简单地在使用基本MongoDB驱动程序和MongoOperations之间进行转换。两个API之间的主要区别在于MongoOperations可以传递域对象而不是DBObject。此外,MongoOperations为Query,Criteria和Update操作提供了流畅的API,而不是填充DBObject来指定这些操作的参数。
在MongoTemplate实例上引用操作的首选方法是通过其接口MongoOperations。
 | 
MongoTemplate使用的默认转换器实现是MappingMongoConverter。虽然MappingMongoConverter可以使用其他元数据来指定对象到文档的映射,但它也可以通过使用一些约定来映射ID和集合名称,从而转换不包含其他元数据的对象。这些约定以及映射注释的使用在“ 映射 ”一章中进行了解释。
在M2版本SimpleMappingConverter中,是默认值,此类现已弃用,因为其功能已被MappingMongoConverter包含。
 | 
MongoTemplate的另一个核心功能是将MongoDB Java驱动程序抛出的异常转换为Spring的可移植数据访问异常层次结构。有关详细信息,请参阅“ 异常翻译 ”。
MongoTemplate提供了许多方便的方法来帮助您轻松执行常见任务。但是,如果需要直接访问MongoDB驱动程序API,可以使用多种Execute回调方法之一。执行回调为您提供对com.mongodb.DBCollection或com.mongodb.DB对象的引用。有关更多信息,请参阅“执行回调”部分。
下一节包含如何在Spring容器的上下文中使用MongoTemplate的示例。
10.4.1.实例化MongoTemplate
您可以使用Java创建和注册MongoTemplate的实例,如以下示例所示:
com.mongodb.Mongo对象并启用Spring的异常转换支持@Configuration
public class AppConfig {
  public @Bean Mongo mongo() throws Exception {
      return new Mongo("localhost");
  }
  public @Bean MongoTemplate mongoTemplate() throws Exception {
      return new MongoTemplate(mongo(), "mydatabase");
  }
}
MongoTemplate有几个重载的构造函数:
- 
MongoTemplate(Mongo mongo, String databaseName):采用com.mongodb.Mongo对象和默认数据库名称进行操作。 - 
MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials)- 添加用于对数据库进行身份验证的用户名和密码。 - 
MongoTemplate(MongoDbFactory mongoDbFactory):获取封装了com.mongodb.Mongo对象,数据库名称以及用户名和密码的MongoDbFactory对象。 - 
MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter):添加MongoConverter以用于映射。 
您还可以使用Spring的XML <beans />架构配置MongoTemplate,如以下示例所示:
<mongo:mongo host="localhost" port="27017"/>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg ref="mongo"/>
  <constructor-arg name="databaseName" value="geospatial"/>
</bean>
创建MongoTemplate时可能要设置的其他可选属性是默认的WriteResultCheckingPolicy,WriteConcern和ReadPreference属性。
在MongoTemplate实例上引用操作的首选方法是通过其接口MongoOperations。
 | 
10.4.2.WriteResultChecking政策
在开发中,如果从任何MongoDB操作返回的com.mongodb.WriteResult包含错误,则记录或抛出异常非常方便。在开发过程中忘记这样做是很常见的,然后最终得到一个看似成功运行的应用程序,但实际上数据库没有根据您的期望进行修改。将MongoTemplate的属性设置为具有以下值的枚举LOG,EXCEPTION或NONE,以记录错误,抛出和异常或不执行任何操作。默认值是使用WriteResultChecking值NONE。
10.4.3.WriteConcern
如果尚未通过更高级别的驱动程序(例如com.mongodb.Mongo)指定,则可以设置MongoTemplate用于写入操作的com.mongodb.WriteConcern属性。如果未设置WriteConcern属性,则默认为MongoDB驱动程序的DB或Collection设置中设置的属性。
10.4.4.WriteConcernResolver
对于要在每个操作基础上设置不同WriteConcern值的更高级情况(用于删除,更新,插入和保存操作),可以在MongoTemplate上配置名为WriteConcernResolver的策略接口。由于MongoTemplate用于持久保存POJO,因此WriteConcernResolver允许您创建可将特定POJO类映射到WriteConcern值的策略。以下清单显示了WriteConcernResolver界面:
public interface WriteConcernResolver {
  WriteConcern resolve(MongoAction action);
}
您可以使用MongoAction参数来确定WriteConcern值,或使用模板本身的值作为默认值。MongoAction包含要写入的集合名称,POJO的java.lang.Class,转换的DBObject,操作(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允许您保存,更新和删除域对象,并将这些对象映射到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类,您可以保存,更新和删除对象,如以下示例所示:
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.Mongo;
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 Mongo(), "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 DBObject 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之间的隐式转换。
上面的示例旨在显示在MongoTemplate上使用保存,更新和删除操作,而不是显示复杂的映射功能。
 | 
在“ 查询文档 ” 一节中更详细地解释了前面示例中使用的查询语法。
10.5.1.如何在映射层中处理_id字段
MongoDB要求所有文档都有_id字段。如果您不提供一个,则驱动程序会为ObjectId分配生成的值。使用MappingMongoConverter时,某些规则控制Java类中的属性如何映射到此_id字段:
- 
用
@Id(org.springframework.data.annotation.Id)注释的属性或字段映射到_id字段。 - 
没有注释但名为
id的属性或字段映射到_id字段。 
下面概述了在使用MappingMongoConverter(MongoTemplate的默认值)时映射到_id文档字段的属性上进行的类型转换(如果有)。
- 
如果可能,使用Spring
Converter<String, ObjectId>将Java类中声明为String的id属性或字段转换为ObjectId并存储为ObjectId。有效的转换规则被委托给MongoDB Java驱动程序。如果无法将其转换为ObjectId,则该值将作为字符串存储在数据库中。 - 
在Java类中声明为
BigInteger的id属性或字段将使用SpringConverter<BigInteger, ObjectId>转换为ObjectId并存储为ObjectId。 
如果Java类中不存在先前规则集中指定的字段或属性,则驱动程序会生成隐式_id文件,但不会映射到Java类的属性或字段。
查询和更新时,MongoTemplate使用与前面规则对应的转换器来保存文档,以便查询中使用的字段名称和类型可以与域类中的字段名称和类型相匹配。
10.5.2.类型映射
MongoDB集合可以包含表示各种类型实例的文档。如果存储类的层次结构或具有类型为Object的属性的类,则此功能非常有用。在后一种情况下,在检索对象时必须正确读取该属性中保存的值。因此,我们需要一种机制来存储类型信息和实际文档。
为了实现这一点,MappingMongoConverter使用MongoTypeMapper抽象,其中DefaultMongoTypeMapper作为其主要实现。它的默认行为是将_class下的完全限定类名存储在文档中,用于顶级文档以及每个值(如果它是复杂类型和declaredproperty类型的子类型)。以下示例(末尾带有JSON表示)显示了映射的工作原理:
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);
{ "_class" : "com.acme.Sample",
  "value" : { "_class" : "com.acme.Person" }
}
如您所见,我们存储实际根类持久性以及嵌套类型的类型信息,因为它很复杂且子类型为Contact。因此,如果您现在使用mongoTemplate.findAll(Object.class, "sample"),我们可以发现存储的文档应该是Sample实例。我们还能够发现value属性实际上是Person。
自定义类型映射
如果要避免将整个Java类名称作为类型信息编写,而是希望使用键,则可以在实体类上使用@TypeAlias注释。如果您需要更多地自定义映射,请查看TypeInformationMapper界面。可以在DefaultMongoTypeMapper配置该接口的实例,然后可以在MappingMongoConverter上配置该实例。以下示例显示如何为实体定义类型别名:
@TypeAlias("pers")
class Person {
}
请注意,生成的文档包含pers作为_class字段中的值。
配置自定义类型映射
以下示例显示如何在MappingMongoConverter中配置自定义MongoTypeMapper:
MongoTypeMapperclass CustomMongoTypeMapper extends DefaultMongoTypeMapper {
  //implement custom type mapping here
}
@Configuration
class SampleMongoConfiguration extends AbstractMongoConfiguration {
  @Override
  protected String getDatabaseName() {
    return "database";
  }
  @Override
  public Mongo mongo() throws Exception {
    return new Mongo();
  }
  @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:
MongoTypeMapper<mongo:mapping-converter type-mapper-ref="customMongoTypeMapper"/>
<bean name="customMongoTypeMapper" class="com.bubu.mongo.CustomMongoTypeMapper"/>
10.5.3.保存和插入文档的方法
MongoTemplate有几种方便的方法可以保存和插入对象。要对转换过程进行更细粒度的控制,您可以使用MappingMongoConverter注册Spring转换器 - 例如Converter<Person, DBObject>和Converter<DBObject, Person>。
| 插入和保存操作之间的区别在于,如果对象尚不存在,则保存操作将执行插入操作。 | 
使用保存操作的简单方法是保存POJO。在这种情况下,集合名称由类的名称(不完全限定)确定。您也可以使用特定的集合名称调用save操作。您可以使用映射元数据覆盖用于存储对象的集合。
插入或保存时,如果未设置Id属性,则假设其值将由数据库自动生成。因此,要成功自动生成ObjectId,类中Id属性或字段的类型必须为String,ObjectId或BigInteger。
以下示例显示如何保存文档并检索其内容:
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);
可以使用以下插入和保存操作:
- 
voidsave(Object objectToSave):将对象保存到默认集合。 - 
voidsave(Object objectToSave, String collectionName):将对象保存到指定的集合。 
还提供了一组类似的插入操作:
- 
voidinsert(Object objectToSave):将对象插入默认集合。 - 
voidinsert(Object objectToSave, String collectionName):将对象插入指定的集合。 
我的文件保存在哪个收藏中?
有两种方法可以管理用于文档的集合名称。使用的默认集合名称是更改为以小写字母开头的类名。因此com.test.Person类存储在person集合中。您可以通过使用@Document注释提供不同的集合名称来自定义此选项。您还可以通过提供自己的集合名称作为所选MongoTemplate方法调用的最后一个参数来覆盖集合名称。
10.5.4.更新集合中的文档
对于更新,您可以使用MongoOperation.updateFirst更新找到的第一个文档,或者可以使用MongoOperation.updateMulti方法更新找到的与查询匹配的所有文档。以下示例显示了所有SAVINGS帐户的更新,我们使用$inc运算符向余额添加一次性$ 50.00奖励:
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提供流畅的样式。
Update类中的方法
您可以在Update类中使用一点“'syntax \ txid'',因为它的方法是链接在一起的。此外,您可以使用public static Update update(String key, Object value)并使用静态导入来启动创建新的Update实例。
Update类包含以下方法:
- 
UpdateaddToSet(String key, Object value)使用$addToSet更新修饰符进行更新 - 
UpdatecurrentDate(String key)使用$currentDate更新修饰符进行更新 - 
UpdatecurrentTimestamp(String key)使用$currentDate更新修饰符更新$typetimestamp - 
Updateinc(String key, Number inc)使用$inc更新修饰符进行更新 - 
Updatemax(String key, Object max)使用$max更新修饰符进行更新 - 
Updatemin(String key, Object min)使用$min更新修饰符进行更新 - 
Update使用$mul更新修饰符乘以(String key, Number multiplier)更新 - 
Updatepop(String key, Update.Position pos)使用$pop更新修饰符进行更新 - 
Update使用$pull更新修饰符拉(String key, Object value)更新 - 
UpdatepullAll(String key, Object[] values)使用$pullAll更新修饰符进行更新 - 
Update使用$push更新修饰符推送(String key, Object value)更新 - 
UpdatepushAll(String key, Object[] values)使用$pushAll更新修饰符进行更新 - 
Update使用$rename更新修饰符重命名(String oldName, String newName)更新 - 
Update使用$set更新修饰符设置(String key, Object value)更新 - 
UpdatesetOnInsert(String key, Object value)使用$setOnInsert更新修饰符进行更新 - 
Updateunset(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);
10.5.6.在集合中查找和复制文档
DBCollection上的findAndModify(…)方法可以更新文档并在单个操作中返回旧文档或新更新的文档。MongoTemplate提供了四种findAndModify重载方法,它们将Query和Update类转换为DBObject并转换为您的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.删除文档的方法
您可以使用五种重载方法之一从数据库中删除对象:
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.8.乐观锁定
@Version注释在MongoDB的上下文中提供类似于JPA的语法,并确保更新仅应用于具有匹配版本的文档。因此,版本属性的实际值将添加到更新查询中,以便在另一个操作同时更改文档时更新不会产生任何影响。在这种情况下,抛出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并将version碰撞到1。 | 
| 4 | 尝试更新仍具有version = 0的先前加载的文档。操作失败,OptimisticLockingFailureException,因为当前version为1。 | 
乐观锁定需要将WriteConcern设置为ACKNOWLEDGED。否则OptimisticLockingFailureException可以被无声吞噬。
 | 
10.6.查询文件
您可以使用Query和Criteria类来表达您的查询。它们具有镜像本机MongoDB运算符名称的方法名称,例如lt,lte,is等。Query和Criteria类遵循流畅的API样式,因此您可以将多个方法标准和查询链接在一起,同时具有易于理解的代码。为了提高可读性,静态导入可以避免使用'new'关键字来创建Query和Criteria实例。您还可以使用BasicQuery从普通JSON字符串创建Query实例,如以下示例所示:
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对象,其名称和年龄存储为集合中的文档,并且每个人都有一个带有余额的嵌入式帐户文档,我们现在可以使用以下代码运行查询:
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);
所有find方法都将Query对象作为参数。此对象定义用于执行查询的条件和选项。通过使用具有名为where的静态工厂方法的Criteria对象来实例化新的Criteria对象来指定条件。我们建议使用org.springframework.data.mongodb.core.query.Criteria.where和Query.query的静态导入来使查询更具可读性。
查询应返回符合指定条件的Person对象列表。本节的其余部分列出了与MongoDB中提供的运算符对应的Criteria和Query类的方法。大多数方法返回Criteria对象,以便为API提供流畅的样式。
标准类的方法
Criteria类提供以下方法,所有这些方法都对应于MongoDB中的运算符:
- 
Criteriaall(Object o)使用$all运算符创建一个条件 - 
Criteria和(String key)将指定key的链式Criteria添加到当前Criteria并返回新创建的Criteria - 
CriteriaandOperator(Criteria… criteria)使用$and运算符为所有提供的条件创建和查询(需要MongoDB 2.0或更高版本) - 
CriteriaelemMatch(Criteria c)使用$elemMatch运算符创建一个标准 - 
Criteriaexists(boolean b)使用$exists运算符创建标准 - 
Criteriagt(Object o)使用$gt运算符创建一个条件 - 
Criteriagte(Object o)使用$gte运算符创建一个条件 - 
Criteriain(Object… o)使用$in运算符为varargs参数创建一个标准。 - 
Criteriain(Collection<?> collection)使用集合使用$in运算符创建标准 - 
Criteria是(Object o)使用$is运算符创建标准 - 
Criterialt(Object o)使用$lt运算符创建标准 - 
Criterialte(Object o)使用$lte运算符创建一个标准 - 
Criteriamod(Number value, Number remainder)使用$mod运算符创建标准 - 
Criteriane(Object o)使用$ne运算符创建一个条件 - 
Criterianin(Object… o)使用$nin运算符创建一个条件 - 
CriterianorOperator(Criteria… criteria)使用$nor运算符为所有提供的条件创建或查询 - 
Criterianot()使用$not元运算符创建一个标准,该运算符直接影响该子句 - 
CriteriaorOperator(Criteria… criteria)使用$or运算符为所有提供的条件创建或查询 - 
Criteriaregex(String re)使用$regex创建标准 - 
Criteriasize(int s)使用$size运算符创建标准 - 
Criteriatype(int t)使用$type运算符创建一个条件 
Criteria类还为地理空间查询提供了以下方法(请参阅GeoSpatial Queries部分以查看它们的运行情况):
- 
Criteria在(Circle circle)内使用$geoWithin $center运算符创建地理空间标准。 - 
Criteria在(Box box)内使用$geoWithin $box操作创建地理空间标准。 - 
CriteriawithinSphere(Circle circle)使用$geoWithin $center运算符创建地理空间标准。 - 
Criterianear(Point point)使用$near操作创建地理空间标准 - 
CriterianearSphere(Point point)使用$nearSphere$center操作创建地理空间标准。这仅适用于MongoDB 1.7及更高版本。 - 
CriteriaminDistance(double minDistance)使用$minDistance操作创建地理空间标准,与$ near一起使用。 - 
CriteriamaxDistance(double maxDistance)使用$maxDistance操作创建地理空间标准,与$ near一起使用。 
10.6.2.查询文件的方法
查询方法需要指定返回的目标类型T,并且它们使用显式集合名称重载,这些查询应该对返回类型指示的集合以外的集合进行操作。使用以下查询方法可以找到一个或多个文档:
- 
findAll:从集合中查询类型为
T的对象列表。 - 
findOne:将集合上的即席查询的结果映射到指定类型的对象的单个实例。
 - 
findById:返回给定ID和目标类的对象。
 - 
find:将集合上的即席查询的结果映射到指定类型的
List。 - 
findAndRemove:将集合上的即席查询的结果映射到指定类型的对象的单个实例。返回与查询匹配的第一个文档,并从数据库中的集合中删除。
 
10.6.3.地理空间查询
MongoDB通过使用$near,$within,geoWithin和$nearSphere等运算符支持地理空间查询。Criteria类提供了特定于地理空间查询的方法。还有一些形状类(Box,Circle和Point)与地理空间相关的Criteria方法结合使用。
要了解如何执行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);
地理附近查询
MongoDB支持在数据库中查询地理位置并同时计算与给定原点的距离。通过地理附近查询,您可以表达查询,例如“查找周围10英里内的所有餐馆”。为了让您这样做,MongoOperations提供了geoNear(…)方法,它们以NearQuery作为参数(以及已经熟悉的实体类型和集合),如以下示例所示:
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构建器API来设置查询,以将给定Point周围的所有Restaurant实例返回到10英里。这里使用的Metrics枚举实际上实现了一个接口,以便其他指标也可以插入到一个距离中。A Metric由乘数支持,以将给定度量的距离值转换为本机距离。此处显示的示例将10视为英里。使用其中一个内置指标(英里和公里)会自动触发在查询上设置的球形标记。如果您想避免这种情况,请将普通double值传递给maxDistance(…)。有关更多信息,请参阅NearQuery和Distance 的JavaDoc。
geo-near操作返回一个封装GeoResult实例的GeoResults包装器对象。包裹GeoResults允许访问所有结果的平均距离。单个GeoResult对象携带找到的实体加上它与原点的距离。
10.6.4.GeoJSON支持
MongoDB支持地理空间数据的GeoJSON和简单(传统)坐标对。这些格式既可用于存储,也可用于查询数据。有关GeoJSON支持的信息,请参阅MongoDB手册以了解要求和限制。
域类中的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;
}
Repository查询方法中的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类型的Repository方法定义允许使用GeoJSON和遗留格式调用它。 | 
| 2 | 使用GeoJSON类型来使用$geometry运算符。 | 
| 3 | 请注意,GeoJSON多边形需要定义闭环。 | 
| 4 | 使用旧格式$polygon运算符。 | 
10.6.5.全文查询
从MongoDB 2.6版开始,您可以使用$text运算符运行全文查询。TextQuery和TextCriteria中提供了特定于全文查询的方法和操作。在进行全文搜索时,请参阅MongoDB参考以了解其行为和限制。
全文检索
在实际使用全文搜索之前,必须正确设置搜索索引。有关如何创建索引结构的更多详细信息,请参阅文本索引。以下示例显示如何设置全文搜索:
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.7.按示例查询
10.7.1.介绍
本章介绍Query by Example并说明如何使用它。
按示例查询(QBE)是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询,并且不需要您编写包含字段名称的查询。实际上,Query by Example不要求您使用特定于商店的查询语言来编写查询。
10.7.2.用法
Query by Example API由三部分组成:
- 
探测器:具有填充字段的域对象的实际示例。
 - 
ExampleMatcher:ExampleMatcher载有关于如何匹配特定字段的详细信息。它可以在多个示例中重用。 - 
Example:Example由探测器和ExampleMatcher组成。它用于创建查询。 
按示例查询非常适合几种用例:
- 
使用一组静态或动态约束查询数据存储。
 - 
频繁重构域对象,而不必担心破坏现有查询。
 - 
独立于底层数据存储API工作。
 
按示例查询也有几个限制:
- 
不支持嵌套或分组的属性约束,例如
firstname = ?0 or (firstname = ?1 and lastname = ?2)。 - 
仅支持字符串的开始/包含/结束/正则表达式匹配以及其他属性类型的精确匹配。
 
在开始使用Query by Example之前,您需要拥有一个域对象。首先,为存储库创建一个接口,如以下示例所示:
public class Person {
  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;
  // … getters and setters omitted
}
前面的示例显示了一个简单的域对象。您可以使用它来创建Example。默认情况下,将忽略具有null值的字段,并使用特定于商店的默认值匹配字符串。可以使用of工厂方法或使用来构建示例ExampleMatcher。Example是不可改变的。以下清单显示了一个简单的示例:
Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)
Example<Person> example = Example.of(person);         (3)
| 1 | 创建域对象的新实例。 | 
| 2 | 设置要查询的属性。 | 
| 3 | 创建Example。 | 
理想情况下,使用存储库执行示例。为此,请让您的存储库接口扩展QueryByExampleExecutor<T>。以下清单显示了QueryByExampleExecutor界面的摘录:
QueryByExampleExecutorpublic 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为字符串匹配,空值处理和特定于属性的设置指定自己的默认值,如以下示例所示:
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()。
您可以为单个属性指定行为(例如“firstname”和“lastname”,或者对于嵌套属性,“address.city”)。您可以使用匹配选项和区分大小写来调整它,如以下示例所示:
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}
配置matcher选项的另一种方法是使用lambda(在Java 8中引入)。此方法创建一个回调,要求实现者修改匹配器。您无需返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用lambdas的匹配器:
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}
Example创建的查询使用配置的合并视图。默认匹配设置可以设置为ExampleMatcher级别,而个别设置可以应用于特定属性路径。除非明确定义,否则ExampleMatcher上设置的设置将由属性路径设置继承。属性修补程序上的设置优先于默认设置。下表描述了各种ExampleMatcher设置的范围:
| 设置 | 范围 | 
|---|---|
空操作  | 
  | 
字符串匹配  | 
  | 
忽略属性  | 
物业路径  | 
区分大小写  | 
  | 
价值转变  | 
物业路径  | 
10.7.4.运行示例
以下示例显示了在使用存储库(在本例中为Person对象)时如何通过示例进行查询:
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使用Repository类型及其集合名称。类型化的ExampleSpec实例使用其类型作为结果类型和Repository实例中的集合名称。
在ExampleSpec中包含null值时,Spring Data Mongo使用嵌入式文档匹配而不是点符号属性匹配。这样做会强制对嵌入文档中的所有属性值和属性顺序进行精确的文档匹配。
 | 
Spring Data MongoDB支持以下匹配选项:
| 匹配 | 逻辑结果 | 
|---|---|
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
10.8.地图减少操作
您可以使用Map-Reduce查询MongoDB,这对批处理,数据聚合以及查询语言无法满足您的需求非常有用。
Spring通过在MongoOperations上提供方法来简化Map-Reduce操作的创建和执行,从而提供与MongoDB Map-Reduce的集成。它可以将Map-Reduce操作的结果转换为POJO,并与Spring的资源抽象集成。这使您可以将JavaScript文件放在文件系统,类路径,HTTP服务器或任何其他Spring资源实现上,然后通过简单的URI样式语法引用JavaScript资源 - 例如classpath:reduce.js;。在文件中外化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);
    }
}
以下reduce函数总结了所有文档中每个字母的出现:
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并捆绑在jar中,因此它们在类路径中可用,您可以按如下方式运行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);
您还可以指定查询以减少送入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.脚本操作
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操作,这种操作与使用SQL的分组查询方式类似,因此与使用Map-Reduce相比,它可能更容易接近。使用组操作确实有一些限制,例如,它在共享环境中不受支持,并且它在单个BSON对象中返回完整的结果集,因此结果应该很小,小于10,000个键。
Spring通过在MongoOperations上提供方法来简化组操作的创建和执行,从而提供与MongoDB组操作的集成。它可以将组操作的结果转换为POJO,还可以与Spring的资源抽象抽象集成。这将允许您将JavaScript文件放在文件系统,类路径,http服务器或任何其他Spring资源实现上,然后通过简单的URI样式语法引用JavaScript资源,例如'classpath:reduce.js;。将JavaScript代码外部化为文件,如果通常更喜欢将它们作为Java字符串嵌入代码中。请注意,如果您愿意,仍可以将JavaScript代码作为Java字符串传递。
10.10.1.示例用法
为了理解组操作如何工作,使用了以下示例,这有点人为。有关更现实的示例,请参阅“MongoDB  - 权威指南”一书。使用以下行创建名为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);
第一个参数是运行组操作的集合的名称,第二个参数是一个流畅的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”字段下的文档被映射到group方法中的第三个参数,在本例中为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将原始结果作为DbObject获取。
MongoOperations上的group方法还有一个额外的方法重载,它允许您指定一个Criteria对象来选择行的子集。使用Criteria对象,使用静态导入的一些语法糖,以及通过Spring资源字符串引用键函数和减少函数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的聚合框架提供支持。
有关详细信息,请参阅MongoDB的聚合框架和其他数据聚合工具的完整参考文档。
10.11.1.基本概念
Spring Data MongoDB中的聚合框架支持基于以下关键抽象:Aggregation,AggregationOperation和AggregationResults。
- 
AggregationAggregation表示MongoDBaggregate操作,并保存聚合管道指令的描述。通过调用Aggregation类的相应newAggregation(…)静态工厂方法来创建聚合,该方法采用AggregateOperation列表和可选输入类。实际的聚合操作由
MongoTemplate的aggregate方法执行,该方法将所需的输出类作为参数。 - 
AggregationOperationAggregationOperation表示MongoDB聚合管道操作,并描述了应在此聚合步骤中执行的处理。虽然您可以手动创建AggregationOperation,但我们建议使用Aggregate类提供的静态工厂方法来构造AggregateOperation。 - 
AggregationResultsAggregationResults是聚合操作结果的容器。它提供对原始聚合结果的访问,以映射对象的DBObject形式和有关聚合的其他信息。以下清单显示了使用MongoDB聚合框架的Spring Data 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(); 
请注意,如果您提供输入类作为newAggregation方法的第一个参数,MongoTemplate将从此类派生输入集合的名称。否则,如果未指定输入类,则必须显式提供输入集合的名称。如果同时提供输入类和输入集合,则后者优先。
10.11.2.支持的聚合操作
MongoDB聚合框架提供以下类型的聚合操作:
- 
管道聚合运算符
 - 
组聚合运算符
 - 
布尔聚合运算符
 - 
比较聚合运算符
 - 
算术聚合运算符
 - 
字符串聚合运算符
 - 
日期聚合运算符
 - 
数组聚合运算符
 - 
条件聚合运算符
 - 
查找聚合运算符
 - 
转换聚合运算符
 
在撰写本文时,我们为Spring Data MongoDB中的以下聚合操作提供支持:
管道聚合运算符  | 
  | 
设置聚合运算符  | 
  | 
组聚合运算符  | 
  | 
算术聚合运算符  | 
  | 
字符串聚合运算符  | 
  | 
比较聚合运算符  | 
  | 
数组聚合运算符  | 
  | 
文字运营商  | 
  | 
日期聚合运算符  | 
  | 
变量运算符  | 
  | 
条件聚合运算符  | 
  | 
类型聚合运算符  | 
  | 
转换聚合运算符  | 
  | 
- 
该操作由Spring Data MongoDB映射或添加。
 
请注意,Spring Data MongoDB当前不支持此处未列出的聚合操作。比较聚合运算符表示为Criteria表达式。
10.11.3.投影表达
投影表达式用于定义作为特定聚合步骤的结果的字段。可以通过Aggregation类的project方法定义投影表达式,方法是传递String对象列表或聚合框架Fields对象。可以使用and(String)方法通过流畅的API使用其他字段扩展投影,并使用as(String)方法进行别名。请注意,您还可以使用聚合框架的Fields.field静态工厂方法定义具有别名的字段,然后可以使用该方法构建新的Fields实例。在后续聚合阶段中对投影字段的引用仅对包含字段的字段名称或其别名(包括新定义的字段及其别名)有效。未包含在投影中的字段无法在以后的聚合阶段中引用。以下列表显示了投影表达式的示例:
// 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")
// 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聚合框架参考文档的相应部分。
10.11.4.分面分类
从版本3.4开始,MongoDB通过使用聚合框架支持分面分类。分面分类使用组合的语义类别(一般或特定主题)来创建完整的分类条目。流经聚合管道的文档被分类为桶。多面分类允许在同一组输入文档上进行各种聚合,而无需多次检索输入文档。
水桶
存储桶操作根据指定的表达式和存储区边界将传入文档分组(称为存储桶)。存储桶操作需要分组字段或分组表达式。您可以使用Aggregate类的bucket()和bucketAuto()方法来定义它们。BucketOperation和BucketAutoOperation可以根据输入文档的聚合表达式公开累积。您可以使用with…()方法和andOutput(String)方法通过流畅的API扩展桶操作以及其他参数。您可以使用as(String)方法为操作设置别名。每个存储桶在输出中表示为文档。
BucketOperation采用一组定义的边界将传入的文档分组到这些类别中。边界需要进行排序。以下清单显示了存储桶操作的一些示例:
// 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可选地采用粒度值,该值指定用于确保计算的边界边缘在首选轮数或10的幂上结束的首选数字序列。以下列表显示了桶操作的示例:
// 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表达式通过andOutputExpression()。
请注意,有关存储区表达式的更多详细信息,请参阅MongoDB聚合框架参考文档的$bucket部分和
 $bucketAuto部分。
多方面聚合
可以使用多个聚合管道来创建多面聚合,以在单个聚合阶段内跨多个维度(或构面)表征数据。多面聚合提供多个过滤器和分类,以指导数据浏览和分析。分面的常见实现是有多少在线零售商通过对产品价格,制造商,尺寸和其他因素应用过滤器来提供缩小搜索结果范围的方法。
您可以使用Aggregation类的facet()方法定义FacetOperation。您可以使用and()方法使用多个聚合管道对其进行自定义。每个子管道在输出文档中都有自己的字段,其结果存储为文档数组。
子管道可以在分组之前投影和过滤输入文档。常见用例包括在分类之前提取日期部分或计算。以下清单显示了构面操作示例:
// 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聚合框架参考文档的$facet部分。
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转换:
| SpEL表达 | 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]}  | 
a + b  | 
{$ add:[$ a,$ b]}  | 
a - b  | 
{$减:[$ a,$ b]}  | 
a * b  | 
{$ multiply:[$ a,$ b]}  | 
a / b  | 
{$ divide:[$ a,$ b]}  | 
一^ B  | 
{$ pow:[$ a,$ b]}  | 
a%b  | 
{$ mod:[$ a,$ b]}  | 
a && b  | 
{$和:[$ a,$ b]}  | 
a || b  | 
{$或:[$ a,$ b]}  | 
!一个  | 
{$ not:[$ a]}  | 
除了上表中显示的转换之外,您还可以使用标准SpEL操作(例如new)(例如)通过名称创建数组和引用表达式(后跟括号中使用的参数)。以下示例显示如何以此方式创建数组:
// { $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操作从输入集合中选择tags字段(这是一个字符串数组)。 - 
使用
unwind操作为tags数组中的每个标记生成新文档。 - 
使用
group操作为我们聚合事件计数的每个tags值定义一个组(通过使用count聚合运算符并在名为n的新字段中收集结果)。 - 
选择
n字段,并为前一个组操作生成的ID字段创建别名(因此调用previousOperation()),名称为tag。 - 
使用
sort操作按生成计数降序对生成的标记列表进行排序。 - 
调用
MongoTemplate上的aggregate方法让MongoDB执行实际的聚合操作,创建的Aggregation作为参数。 
请注意,输入集合被明确指定为aggregate方法的tags参数。如果未明确指定输入集合的名称,则它将从作为第一个参数传递给newAggreation方法的输入类派生。
聚合框架示例2
此示例基于MongoDB聚合框架文档中的“ 最大和最小城市”示例。我们添加了额外的排序,以使用不同的MongoDB版本生成稳定的结果。在这里,我们希望通过使用聚合框架按每个州的人口返回最小和最大的城市。此示例演示分组,排序和投影(选择)。
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类映射给定输入集合的结构。ZipInfoStats类以所需的输出格式定义结构。
上述列表使用以下算法:
- 
使用
group操作从输入集合中定义组。分组标准是state和city字段的组合,它们形成组的ID结构。我们使用sum运算符从分组元素聚合population属性的值,并将结果保存在pop字段中。 - 
使用
sort操作按pop,state和city字段按升序对中间结果进行排序,以便最小的城市位于顶部,最大的城市位于结果的底部。请注意,state和city上的排序是针对组ID字段(MongoDB处理的Spring Data)隐式执行的。 - 
再次使用
group操作将中间结果分组为state。请注意,state再次隐式引用组ID字段。我们在project操作中分别调用last(…)和first(…)运算符来选择最大和最小城市的名称和人口数。 - 
从上一个
group操作中选择state字段。请注意,state再次隐式引用组ID字段。由于我们不希望显示隐式生成的ID,因此我们使用and(previousOperation()).exclude()从上一个操作中排除ID。因为我们想要在输出类中填充嵌套的City结构,所以我们必须使用嵌套方法发出适当的子文档。 - 
在
sort操作中按升序对StateStats的结果列表按其状态名称进行排序。 
请注意,我们将作为第一个参数传递的ZipInfo类的输入集合的名称派生到newAggregation方法。
聚合框架示例3
此示例基于MongoDB聚合框架文档中的人口超过1000万的示例。我们添加了额外的排序,以使用不同的MongoDB版本生成稳定的结果。在这里,我们希望使用聚合框架返回人口超过1000万的所有州。此示例演示分组,排序和匹配(过滤)。
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();
上述列表使用以下算法:
- 
按
state字段对输入集合进行分组,并计算population字段的总和,并将结果存储在新字段"totalPop"中。 - 
除了
"totalPop"字段的升序之外,还通过前一个组操作的id-reference对中间结果进行排序。 - 
使用
match操作过滤中间结果,该操作接受Criteria查询作为参数。 
请注意,我们将作为第一个参数传递的ZipInfo类的输入集合的名称派生到newAggregation方法。
聚合框架示例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<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> resultList = result.getMappedResults();
请注意,我们从作为第一个参数传递的Product类到newAggregation方法派生输入集合的名称。
聚合框架示例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<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> 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<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> 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描述的项目。
10.12.使用自定义转换器覆盖默认映射
要对映射过程进行更细粒度的控制,可以使用MongoConverter实现注册Spring转换器,例如MappingMongoConverter。
在尝试映射对象本身之前,MappingMongoConverter检查是否有任何Spring转换器可以处理特定类。要“劫持”MappingMongoConverter的常规映射策略,可能是为了提高性能或其他自定义映射需求,首先需要创建Spring Converter接口的实现,然后将其注册到MappingConverter。
| 有关Spring类型转换服务的详细信息,请参阅此处的参考文档。 | 
10.12.1.使用已注册的Spring转换器进行保存
以下示例显示了Converter的实现,该实现从Person对象转换为com.mongodb.DBObject:
import org.springframework.core.convert.converter.Converter;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
public class PersonWriteConverter implements Converter<Person, DBObject> {
  public DBObject convert(Person source) {
    DBObject dbo = new BasicDBObject();
    dbo.put("_id", source.getId());
    dbo.put("name", source.getFirstName());
    dbo.put("age", source.getAge());
    return dbo;
  }
}
10.12.2.使用Spring转换器进行阅读
以下示例显示了Converter的实现,该实现从DBObject转换为Person对象:
public class PersonReadConverter implements Converter<DBObject, Person> {
  public Person convert(DBObject source) {
    Person p = new Person((ObjectId) source.get("_id"), (String) source.get("name"));
    p.setAge((Integer) source.get("age"));
    return p;
  }
}
10.12.3.使用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实现启用类路径扫描,如以下示例所示:
<mongo:mapping-converter>
  <mongo:custom-converters base-package="com.acme.**.converters" />
</mongo:mapping-converter>
10.12.4.转换器消歧
通常,我们检查它们转换为的源和目标类型的Converter实现。根据其中一个是MongoDB本身可以处理的类型,我们将转换器实例注册为读取或写入转换器。以下示例显示了编写器转换器和读取转换器(请注意差异在Converter上的限定符的顺序):
// 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.13.索引和收集管理
MongoTemplate提供了一些管理索引和集合的方法。这些方法被收集到名为IndexOperations的辅助接口中。您可以通过调用indexOps方法并传入集合名称或实体的java.lang.Class来访问这些操作(集合名称来自.class,可以是名称或注释元数据) 。
以下清单显示了IndexOperations界面:
public interface IndexOperations {
  void ensureIndex(IndexDefinition indexDefinition);
  void dropIndex(String name);
  void dropAllIndexes();
  void resetIndexCache();
  List<IndexInfo> getIndexInfo();
}
10.13.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"));
10.13.2.访问索引信息
IndexOperations接口具有getIndexInfo方法,该方法返回IndexInfo对象的列表。此列表包含集合上定义的所有索引。以下示例在Person类上定义了一个具有age属性的索引:
template.indexOps(Person.class).ensureIndex(new Index().on("age", Order.DESCENDING).unique(Duplicates.DROP));
List<IndexInfo> indexInfoList = template.indexOps(Person.class).getIndexInfo();
// Contains
// [IndexInfo [fieldSpec={_id=ASCENDING}, name=_id_, unique=false, dropDuplicates=false, sparse=false],
//  IndexInfo [fieldSpec={age=DESCENDING}, name=age_-1, unique=true, dropDuplicates=true, sparse=false]]
10.13.3.使用集合的方法
以下示例显示如何创建集合:
MongoTemplate处理集合DBCollection collection = null;
if (!mongoTemplate.getCollectionNames().contains("MyNewCollection")) {
    collection = mongoTemplate.createCollection("MyNewCollection");
}
mongoTemplate.dropCollection("MyNewCollection");
- 
getCollectionNames:返回一组集合名称。
 - 
collectionExists:检查是否存在具有给定名称的集合。
 - 
createCollection:创建一个无上限的集合。
 - 
dropCollection:删除集合。
 - 
getCollection:按名称获取集合,如果它不存在则创建它。
 
10.14.执行命令
您可以使用MongoTemplate上的executeCommand(…)方法获取MongoDB驱动程序的DB.command( )方法。这些方法还执行异常转换到Spring的DataAccessException层次结构。
10.15.生命周期Events
MongoDB映射框架包括几个org.springframework.context.ApplicationEvent事件,您的应用程序可以通过在ApplicationContext中注册特殊bean来响应这些事件。基于Spring的ApplicationContext事件基础架构,其他产品(如Spring Integration)可以轻松接收这些事件,因为它们是基于Spring的应用程序中众所周知的事件处理机制。
要在对象经过转换过程(将您的域对象转换为com.mongodb.DBObject)之前拦截它,您可以注册一个覆盖onBeforeConvert方法的AbstractMongoEventListener子类。调度事件时,将调用侦听器并在进入转换器之前传递域对象。以下示例显示了如何执行此操作:
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.DBObject。以下示例显示了如何执行此操作:
public class BeforeSaveListener extends AbstractMongoEventListener<Person> {
  @Override
  public void onBeforeSave(BeforeSaveEvent<Person> event) {
    … change values, delete them, whatever …
  }
}
在Spring ApplicationContext中声明这些bean会导致在调度事件时调用它们。
AbstractMappingEventListener中存在以下回调方法:
- 
onBeforeConvert:在对象被MongoConverter转换为DBObject之前,在MongoTemplateinsert,insertList和save操作中调用。 - 
onBeforeSave:在数据库中插入或保存DBObject之前,在MongoTemplateinsert,insertList和save操作中调用。 - 
onAfterSave:在数据库中插入或保存DBObject后,在MongoTemplateinsert,insertList和save操作中调用。 - 
onAfterLoad:在从数据库中检索到DBObject后,在MongoTemplatefind,findAndRemove,findOne和getCollection方法中调用。 - 
onAfterConvert:在从数据库中检索到DBObject之后,在MongoTemplatefind,findAndRemove,findOne和getCollection方法中调用的方法被转换为POJO。 
生命周期事件仅针对根级别类型发出。在文档根目录中用作属性的复杂类型不受事件发布的约束,除非它们是使用@DBRef注释的文档引用。
 | 
10.16.例外翻译
Spring框架为各种数据库和映射技术提供异常转换。传统上这适用于JDBC和JPA。MongoDB的Spring支持通过提供org.springframework.dao.support.PersistenceExceptionTranslator接口的实现将此功能扩展到MongoDB数据库。
映射到Spring 一致的数据访问异常层次结构背后的动机是,您可以编写可移植和描述性的异常处理代码,而无需对MongoDB错误代码进行编码。所有Spring的数据访问异常都从根DataAccessException类继承,因此您可以确保在单个try-catch块中捕获所有与数据库相关的异常。请注意,并非MongoDB驱动程序抛出的所有异常都从MongoException类继承。保留内部异常和消息,以便不丢失任何信息。
MongoExceptionTranslator执行的一些映射是com.mongodb.Network to DataAccessResourceFailureException和MongoException错误代码1003,12001,12010,12011和12012到InvalidDataAccessApiUsageException。查看实现以获取有关映射的更多详细信息。
10.17.执行回调
所有Spring模板类的一个常见设计特征是所有功能都被路由到模板的一个执行回调方法中。这样做有助于确保一致地执行可能需要的异常和任何资源管理。虽然JDBC和JMS比MongoDB更需要此功能,但它仍然提供了一个用于异常转换和日志记录的单点。因此,使用这些执行回调是访问MongoDB驱动程序的DB和DBCollection对象以执行未在MongoTemplate上作为方法公开的非常见操作的首选方法。
以下列表描述了执行回调方法。
- 
<T> Texecute(Class<?> entityClass, CollectionCallback<T> action):为指定类的实体集合执行给定的CollectionCallback。 - 
<T> Texecute(String collectionName, CollectionCallback<T> action):对给定名称的集合执行给定的CollectionCallback。 - 
<T> Texecute(DbCallback<T> action):执行DbCallback,根据需要翻译任何异常。Spring Data MongoDB为版本2.2中引入MongoDB的聚合框架提供支持。 - 
<T> Texecute(String collectionName, DbCallback<T> action):对给定名称的集合执行DbCallback,根据需要翻译任何异常。 - 
<T> TexecuteInSession(DbCallback<T> action):在与数据库相同的连接中执行给定的DbCallback,以确保在写入密集的环境中保持一致性,您可以在其中读取您编写的数据。 
以下示例使用CollectionCallback返回有关索引的信息:
boolean hasIndex = template.execute("geolocation", new CollectionCallbackBoolean>() {
  public Boolean doInCollection(Venue.class, DBCollection collection) throws MongoException, DataAccessException {
    List<DBObject> indexes = collection.getIndexInfo();
    for (DBObject dbo : indexes) {
      if ("location_2d".equals(dbo.get("name"))) {
        return true;
      }
    }
    return false;
  }
});
10.18.GridFS支持
MongoDB支持在其文件系统GridFS中存储二进制文件。Spring Data MongoDB提供了一个GridFsOperations接口以及相应的实现GridFsTemplate,以便您与文件系统进行交互。您可以通过将MongoDbFactory实例和MongoConverter设置为GridFsTemplate实例,如以下示例所示:
class GridFsConfiguration extends AbstractMongoConfiguration {
  // … further configuration omitted
  @Bean
  public GridFsTemplate gridFsTemplate() {
    return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
  }
}
相应的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
                      http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
                      http://www.springframework.org/schema/beans
                      http://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>
现在可以注入模板并用于执行存储和检索操作,如以下示例所示:
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封送。或者,您也可以提供DBObject。
您可以通过find(…)或getResources(…)方法从文件系统中读取文件。我们先来看看find(…)方法。您可以找到单个文件,也可以找到与Query匹配的多个文件。您可以使用GridFsCriteria帮助程序类来定义查询。它提供静态工厂方法来封装默认元数据字段(例如whereFilename()和whereContentType())或自定义的字段到whereMetaData()。以下示例显示如何使用GridFsTemplate查询文件:
class GridFsClient {
  @Autowired
  GridFsOperations operations;
  @Test
  public void findFilesInGridFs {
    List<GridFSDBFile> result = operations.find(query(whereFilename().is("filename.txt")))
  }
}
目前,MongoDB不支持在从GridFS检索文件时定义排序条件。因此,忽略了传递到find(…)方法的Query实例上定义的任何排序条件。
 | 
从GridF读取文件的另一个选项是使用ResourcePatternResolver接口引入的方法。它们允许将Ant路径传递给方法,从而可以检索与给定模式匹配的文件。以下示例显示如何使用GridFsTemplate读取文件:
class GridFsClient {
  @Autowired
  GridFsOperations operations;
  @Test
  public void readFilesFromGridFs {
    GridFsResources[] txtFiles = operations.getResources("*.txt");
  }
}
GridFsOperations扩展ResourcePatternResolver并将GridFsTemplate(例如)插入ApplicationContext以从MongoDB数据库中读取Spring配置文件。
11. MongoDB Repositories
11.1.介绍
本章指出了MongoDB的存储库支持的专业性。本章以使用Spring Data Repositories中解释的核心存储库支持为基础。你应该对那里解释的基本概念有充分的理解。
11.2.用法
要访问存储在MongoDB中的域实体,您可以使用我们先进的存储库支持,从而显着简化实现。为此,请为存储库创建一个接口,如以下示例所示:
public class Person {
  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;
  // … getters and setters omitted
}
请注意,前面示例中显示的域类型具有类型为ObjectId的名为id的属性。MongoTemplate中使用的默认序列化机制(支持存储库支持)将名为id的属性视为文档ID。目前,我们支持String,ObjectId和BigInteger作为ID类型。现在我们有了一个域对象,我们可以定义一个使用它的接口,如下所示:
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
  // additional custom query methods go here
}
现在,此接口仅用于提供类型信息,但我们稍后可以添加其他方法。为此,请在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
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/data/mongo
    http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">
  <mongo:mongo id="mongo" />
  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongo" />
    <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注释。该注释具有与命名空间元素相同的属性。如果未配置基本软件包,则基础结构将扫描带注释的配置类的软件包。以下示例显示如何将Java配置用于存储库:
@Configuration
@EnableMongoRepositories
class ApplicationConfig extends AbstractMongoConfiguration {
  @Override
  protected String getDatabaseName() {
    return "e-store";
  }
  @Override
  public Mongo mongo() throws Exception {
    return new Mongo();
  }
  @Override
  protected String getMappingBasePackage() {
    return "com.oreilly.springdata.mongodb"
  }
}
由于我们的域存储库扩展了PagingAndSortingRepository,因此它为您提供了CRUD操作以及对实体进行分页和排序访问的方法。使用存储库实例只是将其注入客户端的依赖性问题。因此,访问页面大小为10的Person对象的第二页将类似于以下代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PersonRepositoryTests {
    @Autowired PersonRepository repository;
    @Test
    public void readsFirstPageCorrectly() {
      Page<Person> persons = repository.findAll(new PageRequest(0, 10));
      assertThat(persons.isFirstPage(), is(true));
    }
}
上面的示例使用Spring的单元测试支持创建应用程序上下文,该支持将测试用例执行基于注释的依赖注入。在测试方法中,我们使用存储库来查询数据存储区。我们将存储库交给PageRequest实例,该实例请求页面大小为10的Person对象的第一页。
11.3.查询方法
您通常在存储库上触发的大多数数据访问操作都会导致对MongoDB数据库执行查询。定义此类查询是在存储库接口上声明方法的问题,如以下示例所示:
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)
    Stream<Person> findAllBy();                                        (4)
}
| 1 | findByLastname方法显示具有给定姓氏的所有人的查询。通过解析可以与And和Or连接的约束的方法名称来派生查询。因此,方法名称导致查询表达式为{"lastname" : lastname}。 | 
| 2 | 将分页应用于查询。您可以使用Pageable参数为方法签名配备,让方法返回Page实例,Spring Data自动为查询分页。 | 
| 3 | 显示您可以基于非基本类型的属性进行查询。如果找到多个匹配项,则抛出IncorrectResultSizeDataAccessException。 | 
| 4 | 使用First关键字将查询限制为仅第一个结果。与<3>不同,如果找到多个匹配项,此方法不会引发异常。 | 
| 5 | 使用Java 8 Stream,在迭代流时读取和转换单个元素。 | 
我们不支持引用在域类中映射为DBRef的参数。
 | 
下表显示了查询方法支持的关键字:
| 关键词 | 样品 | 逻辑结果 | 
|---|---|---|
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
字符串上的  | 
  | 
  | 
字符串上的  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
11.3.1.Repository删除查询
上表中的关键字可与delete…By或remove…By结合使用,以创建删除匹配文档的查询。
Delete…By查询public interface PersonRepository extends MongoRepository<Person, String> {
  List <Person> deleteByLastname(String lastname);
  Long deletePersonByLastname(String lastname);
}
使用返回类型List会在实际删除之前检索并返回所有匹配的文档。数字返回类型直接删除匹配的文档,返回删除的文档总数。
11.3.2.地理空间Repository查询
正如您在上面的关键字表中看到的,一些关键字在MongoDB查询中触发了地理空间操作。Near关键字允许进一步修改,如下面的几个示例所示。
以下示例显示如何定义near查询,以查找给定点的给定距离的所有人:
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,如下例所示:
Distance和MetricsPoint 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指的是测量系统的概念,无论您使用哪种系统。)
在目标属性上使用@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)会强制使用$nearSphere运算符。
 | 
地理附近查询
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);
}
11.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查询字符串。
String参数值在绑定过程中被转义,这意味着无法通过参数添加MongoDB特定的运算符。
 | 
您还可以使用filter属性来限制映射到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。
11.3.4.具有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。Query 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());
  }
}
自己引导MongoRepositoryFactory不是应用程序上下文感知,需要进一步配置才能获取查询SPI扩展。
 | 
11.3.5.类型安全的查询方法
MongoDB存储库支持与Querydsl项目集成,后者提供了一种执行类型安全查询的方法。引用项目描述,“不是将查询编写为内联字符串或将它们外部化为XML文件,而是通过流畅的API构建它们。” 它提供以下功能:
- 
IDE中的代码完成(所有属性,方法和操作都可以在您喜欢的Java IDE中扩展)。
 - 
几乎没有语法无效的查询允许(所有级别的类型安全)。
 - 
可以安全地引用域类型和属性 - 不涉及任何字符串!
 - 
更好地适应重构域类型的更改。
 - 
增量查询定义更容易。
 
有关如何使用Maven或Ant为基于APT的代码生成引导环境的信息,请参阅QueryDSL文档。
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"),
                                       new PageRequest(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
}
11.3.6.全文搜索查询
MongoDB的全文搜索功能是特定于商店的,因此可以在MongoRepository而不是更通用的CrudRepository上找到。我们需要一个带有全文索引的文档(请参阅“ 文本索引 ”以了解如何创建全文索引)。
MongoRepository上的其他方法将TextCriteria作为输入参数。除了那些显式方法之外,还可以添加TextCriteria派生的存储库方法。该标准作为额外的AND标准添加。一旦实体包含@TextScore  - 注释属性,就可以检索文档的全文分数。此外,@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 = new Sort("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, new PageRequest(1, 1, sort));
List<FullTextDocument> result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);
11.3.7.预测
Spring Data查询方法通常返回由存储库管理的聚合根的一个或多个实例。但是,有时可能需要根据这些类型的某些属性创建投影。Spring Data允许建模专用返回类型,以更有选择地检索托管聚合的部分视图。
想象一下存储库和聚合根类型,例如以下示例:
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 NamesOnly {
  String getFirstname();
  String getLastname();
}
这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。这样做可以添加查询方法,如下所示:
interface PersonRepository extends Repository<Person, UUID> {
  Collection<NamesOnly> findByLastname(String lastname);
}
查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对暴露方法的调用转发给目标对象。
可以递归使用预测。如果您还要包含一些Address信息,请为此创建一个投影接口,并从getAddress()的声明返回该接口,如以下示例所示:
interface PersonSummary {
  String getFirstname();
  String getLastname();
  AddressSummary getAddress();
  interface AddressSummary {
    String getCity();
  }
}
在方法调用上,获取目标实例的address属性并依次包装到投影代理中。
封闭式预测
其访问器方法都与目标聚合的属性匹配的投影接口被认为是封闭投影。以下示例(我们在本章前面也使用过)是一个封闭的投影:
interface NamesOnly {
  String getFirstname();
  String getLastname();
}
如果使用闭合投影,Spring Data可以优化查询执行,因为我们知道支持投影代理所需的所有属性。有关详细信息,请参阅参考文档中特定于模块的部分。
公开预测
投影接口中的访问器方法也可用于通过使用@Value注释计算新值,如以下示例所示:
interface NamesOnly {
  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
  …
}
target变量中提供了支持投影的聚合根。使用@Value的投影界面是开放投影。在这种情况下,Spring Data无法应用查询执行优化,因为SpEL表达式可以使用聚合根的任何属性。
@Value中使用的表达式不应该太复杂 - 您希望避免在String变量中编程。对于非常简单的表达式,一个选项可能是采用默认方法(在Java 8中引入),如以下示例所示:
interface NamesOnly {
  String getFirstname();
  String getLastname();
  default String getFullName() {
    return getFirstname.concat(" ").concat(getLastname());
  }
}
这种方法要求您能够纯粹基于投影接口上公开的其他访问器方法实现逻辑。第二个更灵活的选项是在Spring bean中实现自定义逻辑,然后从SpEL表达式调用它,如以下示例所示:
@Component
class MyBean {
  String getFullName(Person person) {
    …
  }
}
interface NamesOnly {
  @Value("#{@myBean.getFullName(target)}")
  String getFullName();
  …
}
注意SpEL表达式如何引用myBean并调用getFullName(…)方法并将投影目标转发为方法参数。由SpEL表达式评估支持的方法也可以使用方法参数,然后可以从表达式引用它们。方法参数可通过名为args的Object数组获得。以下示例显示如何从args数组中获取方法参数:
interface NamesOnly {
  @Value("#{args[0] + ' ' + target.firstname + '!'}")
  String getSalutation(String prefix);
}
同样,对于更复杂的表达式,您应该使用Spring bean并让表达式调用方法,如前所述。
基于类别的预测(DTO)
定义投影的另一种方法是使用值类型DTO(数据传输对象),它包含应该检索的字段的属性。这些DTO类型可以与投影界面完全相同的方式使用,除了不发生代理并且不能应用嵌套投影。
如果存储通过限制要加载的字段来优化查询执行,则要加载的字段将根据公开的构造函数的参数名称确定。
以下示例显示了投影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
}
| 
 避免投影DTO的样板代码 
您可以使用Project Lombok显着简化DTO的代码,它提供 
默认情况下,字段为  | 
动态预测
到目前为止,我们已经使用投影类型作为集合的返回类型或元素类型。但是,您可能希望选择要在调用时使用的类型(这使其成为动态类型)。要应用动态投影,请使用查询方法,如以下示例中所示:
interface PersonRepository extends Repository<Person, UUID> {
  <T> Collection<T> findByLastname(String lastname, Class<T> type);
}
这样,该方法可用于按原样或应用投影获取聚合,如以下示例所示:
void someMethod(PersonRepository people) {
  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);
  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
11.4.CDI集成
存储库接口的实例通常由容器创建,Spring是使用Spring Data时最自然的选择。从版本1.3.0开始,Spring Data MongoDB附带了一个自定义CDI扩展,允许您在CDI环境中使用存储库抽象。扩展是JAR的一部分。要激活它,请将Spring Data MongoDB JAR放入类路径中。您现在可以通过为MongoTemplate实现CDI Producer来设置基础结构,如以下示例所示:
class MongoTemplateProducer {
    @Produces
    @ApplicationScoped
    public MongoOperations createMongoTemplate() throws UnknownHostException, MongoException {
        MongoDbFactory factory = new SimpleMongoDbFactory(new Mongo(), "database");
        return new MongoTemplate(factory);
    }
}
Spring Data MongoDB CDI扩展获取了作为CDI bean的MongoTemplate,并且只要容器请求存储库类型的bean,就为Spring Data存储库创建代理。因此,获取Spring Data存储库的实例是声明@Inject -ed属性的问题,如以下示例所示:
class RepositoryClient {
  @Inject
  PersonRepository repository;
  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}
12.审计
12.1.基本
Spring Data提供了复杂的支持,以透明地跟踪谁创建或更改了实体以及何时发生了更改。要从该功能中受益,您必须为您的实体类配备审计元数据,该元数据可以使用注释或通过实现接口来定义。
12.1.1.基于注释的审计元数据
我们提供@CreatedBy和@LastModifiedBy来捕获创建或修改实体的用户以及@CreatedDate和@LastModifiedDate以捕获更改发生的时间。
class Customer {
  @CreatedBy
  private User user;
  @CreatedDate
  private DateTime createdDate;
  // … further properties omitted
}
如您所见,可以有选择地应用注释,具体取决于您要捕获的信息。捕获何时进行更改的注释可用于Joda-Time,DateTime,遗留Java Date和Calendar,JDK8日期和时间类型以及long或Long。
12.1.2.基于接口的审计元数据
如果您不想使用注释来定义审核元数据,可以让您的域类实现Auditable接口。它公开了所有审计属性的setter方法。
还有一个便利基类AbstractAuditable,您可以扩展它以避免手动实现接口方法。这样做会增加域类与Spring Data的耦合,这可能是您想要避免的。通常,基于注释的定义审计元数据的方式是优选的,因为它具有较小的侵入性和更灵活性。
12.1.3.AuditorAware
如果您使用@CreatedBy或@LastModifiedBy,审计基础架构需要以某种方式了解当前主体。为此,我们提供了一个AuditorAware<T> SPI接口,您必须实现该接口,以告知基础架构当前用户或系统与应用程序交互的人员。泛型类型T定义了使用@CreatedBy或@LastModifiedBy注释的属性的类型。
以下示例显示了使用Spring Security的Authentication对象的接口的实现:
class SpringSecurityAuditorAware implements AuditorAware<User> {
  public User getCurrentAuditor() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }
    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}
该实现访问Spring Security提供的Authentication对象,并查找您在UserDetailsService实现中创建的自定义UserDetails实例。我们在此假设您通过UserDetails实现公开域用户,但是根据找到的Authentication,您也可以从任何地方查找它。
12.2.MongoDB的常规审计配置
要激活审核功能,请将Spring Data Mongo auditing命名空间元素添加到您的配置中,如以下示例所示:
<mongo:auditing mapping-context-ref="customMappingContext" auditor-aware-ref="yourAuditorAwareImpl"/>
从Spring Data MongoDB 1.4开始,可以通过使用@EnableMongoAuditing注释注释配置类来启用审计,如以下示例所示:
@Configuration
@EnableMongoAuditing
class Config {
  @Bean
  public AuditorAware<AuditableUser> myAuditorProvider() {
      return new AuditorAwareImpl();
  }
}
如果将AuditorAware类型的bean公开给ApplicationContext,则审计基础结构会自动选择它并使用它来确定要在域类型上设置的当前用户。如果您在ApplicationContext中注册了多个实现,则可以通过显式设置@EnableMongoAuditing的auditorAwareRef属性来选择要使用的实现。
13.制图
MappingMongoConverter提供了丰富的映射支持。MappingMongoConverter具有丰富的元数据模型,该模型提供了将域对象映射到MongoDB文档的完整功能集。通过在域对象上使用注释来填充映射元数据模型。但是,基础结构不限于使用注释作为元数据信息的唯一来源。MappingMongoConverter还允许您通过遵循一组约定将对象映射到文档而不提供任何其他元数据。
本节介绍MappingMongoConverter的功能,包括如何使用约定将对象映射到文档以及如何使用基于注释的映射元数据覆盖这些约定。
SimpleMongoConverter已在Spring Data MongoDB M3中弃用,因为它的所有功能都已归入MappingMongoConverter。
 | 
13.1.基于公约的映射
MappingMongoConverter有一些约定,用于在没有提供额外的映射元数据时将对象映射到文档。惯例是:
- 
短Java类名称按以下方式映射到集合名称。类
com.bigbank.SavingsAccount映射到savingsAccount集合名称。 - 
所有嵌套对象都作为嵌套对象存储在文档中,而不是 DBRefs。
 - 
转换器使用向其注册的任何Spring转换器来覆盖对象属性到文档字段和值的默认映射。
 - 
对象的字段用于转换文档中的字段。不使用公共
JavaBean属性。 - 
如果您有一个非零参数构造函数,其构造函数参数名称与document的顶级字段名称匹配,则使用该构造函数。否则,使用零参数构造函数。如果存在多个非零参数构造函数,则会抛出异常。
 
13.1.1.如何在映射层中处理_id字段。
MongoDB要求所有文档都有_id字段。如果您没有提供,则驱动程序将为ObjectId分配生成的值。“_id”字段可以是除数组之外的任何类型,只要它是唯一的。驱动程序自然支持所有原始类型和日期。使用MappingMongoConverter时,某些规则会控制Java类中的属性如何映射到此_id字段。
以下概述了将哪个字段映射到_id文档字段:
- 
注释为
@Id(org.springframework.data.annotation.Id)的字段将映射到_id字段。 - 
没有注释但名为
id的字段将映射到_id字段。 - 
标识符的默认字段名称是
_id,可以通过@Field注释进行自定义。 
| 字段定义 | 在MongoDB中生成Id-Fieldname | 
|---|---|
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
下面概述了将在映射到_id文档字段的属性上执行的类型转换(如果有)。
- 
如果名为
id的字段在Java类中声明为String或BigInteger,则它将转换为ObjectId并存储为ObjectId。ObjectId作为字段类型也是有效的。如果在应用程序中为id指定值,则会向MongoDBdriver检测到ObjectId的转换。如果指定的id值无法转换为ObjectId,则该值将按原样存储在文档的_id字段中。 - 
如果名为
idid字段的字段未在Java类中声明为String,BigInteger或ObjectID,则应在应用程序中为其分配值,以便可以在文档的_id字段中“按原样”存储它。 - 
如果Java类中不存在名为
id的字段,则驱动程序将生成隐式_id文件,但不会映射到Java类的属性或字段。 
查询和更新MongoTemplate时,将使用转换器处理与上述保存文档规则相对应的Query和Update对象的转换,以便查询中使用的字段名称和类型能够匹配什么是你的域类。
13.2.数据映射和类型转换
本节介绍如何将类型映射到MongoDB表示和从MongoDB表示映射。Spring Data MongoDB支持所有可以表示为BSON的类型,MongoDB的内部文档格式。除了这些类型之外,Spring Data MongoDB还提供了一组内置转换器来映射其他类型。您可以提供自己的转换器来调整类型转换。有关更多详细信息,请参阅使用显式转换器覆盖映射。
以下提供了每种可用类型转换的示例:
| 类型 | 类型转换 | 样品 | 
|---|---|---|
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本机  | 
  | 
  | 
本机  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
数组,  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
本地人  | 
  | 
  | 
转换器  | 
  | 
  | 
转换器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
  | 
变流器  | 
  | 
13.3.映射配置
除非明确配置,否则在创建MongoTemplate时默认会创建MappingMongoConverter的实例。您可以创建自己的MappingMongoConverter实例。通过这样做,您可以指定可以在类路径中找到域类的位置,以便Spring Data MongoDB可以提取元数据并构造索引。此外,通过创建自己的实例,您可以注册Spring转换器以将特定类映射到数据库或从数据库映射特定类。
您可以使用基于Java或基于XML的元数据配置MappingMongoConverter以及com.mongodb.Mongo和MongoTemplate。以下示例使用Spring基于Java的配置:
@Configuration
public class GeoSpatialAppConfig extends AbstractMongoConfiguration {
  @Bean
  public Mongo mongo() throws Exception {
    return new Mongo("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.Mongo的方法以及提供数据库名称。AbstractMongoConfiguration还有一个名为getMappingBasePackage(…)的方法,您可以覆盖它以告诉转换器在哪里扫描使用@Document注释注释的类。
您可以通过覆盖customConversions方法将其他转换器添加到转换器。前面的示例中还显示了LoggingEventListener,它记录了发布到Spring的ApplicationContextEvent基础结构上的MongoMappingEvent个实例。
AbstractMongoConfiguration创建一个MongoTemplate实例,并将其注册到名为mongoTemplate的容器中。
 | 
Spring的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 http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  <!-- Default bean name is 'mongo' -->
  <mongo:mongo host="localhost" port="27017"/>
  <mongo:db-factory dbname="database" mongo-ref="mongo"/>
  <!-- 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注释注释的类。
13.4.基于元数据的映射
要充分利用Spring Data MongoDB支持中的对象映射功能,您应该使用@Document注释来注释您的映射对象。虽然映射框架没有必要使用此注释(您的POJO已正确映射,即使没有任何注释),但它允许类路径扫描程序查找并预处理域对象以提取必要的元数据。如果您不使用此批注,则应用程序在您第一次存储域对象时会略微降低性能,因为映射框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何坚持他们。以下示例显示了一个域对象:
package com.mycompany.domain;
@Document
public class Person {
  @Id
  private ObjectId id;
  @Indexed
  private Integer ssn;
  private String firstName;
  @Indexed
  private String lastName;
}
@Id注释告诉映射器要将哪个属性用于MongoDB _id属性,@Indexed注释告诉映射框架在文档的该属性上调用createIndex(…),进行搜索快点。
 | 
仅对使用@Document注释的类型执行自动索引创建。
 | 
13.4.1.映射注释概述
MappingMongoConverter可以使用元数据来驱动对象到文档的映射。可以使用以下注释:
- 
@Id:在实地一级应用,以标记用于身份目的的字段。 - 
@Document:在类级别应用以指示此类是映射到数据库的候选者。您可以指定将存储数据库的集合的名称。 - 
@DBRef:在现场应用以表明它将使用com.mongodb.DBRef存储。 - 
@Indexed:应用于现场级别来描述如何索引该字段。 - 
@CompoundIndex:在类型级别应用以声明复合索引 - 
@GeoSpatialIndexed:应用于实地层面来描述如何对该领域进行地理索引。 - 
@TextIndexed:在字段级别应用以标记要包含在文本索引中的字段。 - 
@Language:在字段级别应用以设置文本索引的语言覆盖属性。 - 
@Transient:默认情况下,所有私有字段都映射到文档,此批注将排除应用它的字段排除在数据库中 - 
@PersistenceConstructor:标记给定的构造函数 - 甚至是受保护的构造函数 - 在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的DBObject中的键值。 - 
@Value:这个注释是Spring Framework的一部分。在映射框架中,它可以应用于构造函数参数。这使您可以使用Spring表达式语言语句转换在数据库中检索的键值,然后再将其用于构造域对象。为了引用给定文档的属性,必须使用如下表达式:@Value("#root.myProperty")其中root指的是给定文档的根。 - 
@Field:在字段级应用并描述字段的名称,因为它将在MongoDB BSON文档中表示,因此允许名称与类的字段名不同。 - 
@Version:在现场级应用用于乐观锁定并检查保存操作的修改。初始值为zero,每次更新时都会自动触发。 
映射元数据基础结构在与技术无关的单独的spring-data-commons项目中定义。特定的子类在MongoDB支持中用于支持基于注释的元数据。如果有需求,也可以采取其他策略。
以下是更复杂映射的示例。
@Document
@CompoundIndexes({
    @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
13.4.2.定制对象构造
映射子系统允许通过使用@PersistenceConstructor注释对构造函数进行批注来自定义对象构造。用于构造函数参数的值按以下方式解析:
- 
如果参数使用
@Value注释进行注释,则会计算给定表达式,并将结果用作参数值。 - 
如果Java类型具有名称与输入文档的给定字段匹配的属性,则它的属性信息用于选择适当的构造函数参数以将输入字段值传递给。仅当参数名称信息存在于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
}
DBObject input = new BasicDBObject("id", "4711");
input.put("unitPrice", 2.5);
input.put("qty",5);
OrderItem item = converter.read(OrderItem.class, input);
如果无法解析给定的属性路径,quantity参数的@Value注释中的SpEL表达式将回退到值0。
 | 
可以在MappingMongoConverterUnitTests测试套件中找到使用@PersistenceConstructor注释的其他示例。
13.4.3.复合指数
还支持复合索引。它们是在类级别定义的,而不是在单个属性上定义的。
| 复合索引对于提高涉及多个字段的条件的查询的性能非常重要 | 
这是一个按升序创建lastName和age降序的复合索引的示例:
package com.mycompany.domain;
@Document
@CompoundIndexes({
    @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;
}
13.4.4.文字索引
| mongodb v.2.4默认禁用文本索引功能。 | 
创建文本索引允许将多个字段累积到可搜索的全文索引中。每个集合只能有一个文本索引,因此标有@TextIndexed的所有字段都会合并到此索引中。Properties可以加权以影响排名结果的文档分数。文本索引的默认语言是英语。要更改默认语言,请将language属性设置为您想要的任何语言(例如,@Document(language="spanish"))。使用名为language或@Language的属性可以在每个文档库上定义语言覆盖。以下示例显示如何创建文本索引并将语言设置为西班牙语:
@Document(language = "spanish")
class SomeEntity {
    @TextIndexed String foo;
    @Language String lang;
    Nested nested;
}
class Nested {
    @TextIndexed(weight=5) String bar;
    String roo;
}
13.4.5.使用DBRefs
映射框架不必存储嵌入在文档中的子对象。您也可以单独存储它们并使用DBRef来引用该文档。当从MongoDB加载对象时,会急切地解析这些引用,以便您返回一个映射对象,该对象看起来与嵌入在主文档中的对象相同。
下面的示例使用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或类似的机制,因为对象列表告诉映射框架您想要一对多的关系。当对象存储在MongoDB中时,有一个DBRef列表而不是Account对象本身。
映射框架不处理级联保存。如果更改Person对象引用的Account对象,则必须单独保存Account对象。在Person对象上调用save不会自动将Account对象保存在accounts属性中。
 | 
13.4.6.映射框架Events
Events在映射过程的整个生命周期中被触发。这在Lifecycle Events部分中进行了描述。
在Spring ApplicationContext中声明这些bean会导致在调度事件时调用它们。
13.4.7.使用显式转换器覆盖映射
在存储和查询对象时,让MongoConverter实例处理所有Java类型到DBObjects的映射是很方便的。但是,有时您可能希望MongoConverter执行大部分工作,但允许您有选择地处理特定类型的转换或优化性能。
要自己选择性地处理转换,请使用MongoConverter注册一个或多个org.springframework.core.convert.converter.Converter个实例。
| Spring 3.0引入了一个core.convert包,它提供了一个通用的类型转换系统。这在Spring参考文档标题为“Spring类型转换”的部分中有详细描述。 | 
您可以使用AbstractMongoConfiguration中的customConversions方法配置转换器。本章开头的示例显示了如何使用Java和XML执行配置。
下面是一个Spring转换器实现的示例,它从DBObject转换为Person POJO。
@ReadingConverter
 public class PersonReadConverter implements Converter<DBObject, Person> {
  public Person convert(DBObject source) {
    Person p = new Person((ObjectId) source.get("_id"), (String) source.get("name"));
    p.setAge((Integer) source.get("age"));
    return p;
  }
}
这是一个从Person转换为DBObject的示例。
@WritingConverter
public class PersonWriteConverter implements Converter<Person, DBObject> {
  public DBObject convert(Person source) {
    DBObject dbo = new BasicDBObject();
    dbo.put("_id", source.getId());
    dbo.put("name", source.getFirstName());
    dbo.put("age", source.getAge());
    return dbo;
  }
}
14.跨店支持
有时您需要将数据存储在多个数据存储中,并且这些数据存储可以是不同类型的。一个可能是关系,而另一个可能是文档存储。对于这个用例,我们在MongoDB支持中创建了一个单独的模块来处理我们称之为跨存储支持的模块。当前实现基于JPA作为关系数据库的驱动程序,我们允许实体中的选择字段存储在Mongo数据库中。除了允许您将数据存储在两个存储中之外,我们还协调非事务性MongoDB存储的持久性操作以及关系数据库的事务生命周期。
14.1.跨存储配置
假设您有一个正常工作的JPA应用程序,并且想为MongoDB添加一些跨存储持久性,那么您需要添加什么来配置?
首先,您需要在跨存储模块上添加依赖项。如果您使用Maven,则可以将以下依赖项添加到您的pom:
spring-data-mongodb-cross-store依赖性<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 http://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>部分添加一个额外的插件,如下所示:
<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 http://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代码段添加到您的应用程序上下文中:
<?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
    http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">
  ...
  <!--  Mongo config -->
  <mongo:mongo host="localhost" port="27017"/>
  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongo" ref="mongo"/>
    <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>
14.2.编写跨存储应用程序
我们假设您有一个有效的JPA应用程序,因此我们仅介绍在Mongo数据库中保留实体部分所需的其他步骤。为此,您需要标识要保留的字段。它应该是一个域类,并遵循前面章节中介绍的Mongo映射支持的一般规则。要在MongoDB中保留的字段应使用@RelatedDocument注释进行注释。这真的是你需要做的。跨店方面负责其余部分,包括:
- 
使用
@Transient标记字段,以便JPA不会保留该字段 - 
跟踪对字段值所做的任何更改,并在成功完成事务后将它们写入数据库
 - 
第一次在应用程序中使用该值时从MongoDB加载文档。
 
以下示例显示了具有使用@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的域类:
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实体的一些元数据)在以JPA Entity类的完全限定名称命名的MongoDB集合中。以下代码显示如何使用MongoDB配置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中:
{ "_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" }
15.记录支持
在maven模块“spring-data-mongodb-log4j”中提供了Log4j的appender。注意,不依赖于其他Spring Mongo模块,只有MongoDB驱动程序。
15.1.MongoDB Log4j配置
这是一个示例配置
log4j.rootCategory=INFO, mongo
log4j.appender.mongo=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender
log4j.appender.mongo.layout=org.apache.log4j.PatternLayout
log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.mongo.host = localhost
log4j.appender.mongo.port = 27017
log4j.appender.mongo.database = logs
log4j.appender.mongo.collectionPattern = %X{year}%X{month}
log4j.appender.mongo.applicationId = my.application
log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE
log4j.category.org.apache.activemq=ERROR
log4j.category.org.springframework.batch=DEBUG
log4j.category.org.springframework.data.document.mongodb=DEBUG
log4j.category.org.springframework.transaction=INFO
除了主机和端口之外,重要的配置是数据库和collectionPattern。变量year,month,day和hour可供您用于形成集合名称。这是为了支持在对应于特定时间段的集合中对日志信息进行分组的通用约定,例如每天的集合。
还有一个applicationId放入存储的消息中。从日志记录中存储的文档为以下键:level,name,applicationId,timestamp,properties,traceback和message。
16. JMX支持
对MongoDB的JMX支持公开了在单个MongoDB服务器实例的admin数据库上执行'serverStatus'命令的结果。它还公开了一个管理MBean MongoAdmin,它允许您执行管理操作,例如删除或创建数据库。JMX功能基于Spring Framework中提供的JMX功能集。有关详细信息,请参见此处
16.1.MongoDB JMX配置
Spring的Mongo命名空间允许您启用JMX功能,如以下示例所示:
<?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
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/data/mongo
    http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <!-- Default bean name is 'mongo' -->
    <mongo:mongo 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的以下屏幕截图显示了生成的配置:
17. MongoDB 3.0支持
Spring Data当使用MMap.v1或WiredTiger存储引擎连接到运行MMap.v1的MongoDB 2.6 / 3.0服务器或MongoDB服务器3.0时,MongoDB需要MongoDB Java驱动程序代3。
| 有关这些引擎之间的主要差异,请参阅特定于驱动程序和数据库的文档。 | 
| 在使用3.x MongoDB Java驱动程序时不再有效的操作已在Spring Data内弃用,并将在后续版本中删除。 | 
17.1.使用Spring Data MongoDB和MongoDB 3.0
本节的其余部分描述了如何将Spring Data MongoDB与MongoDB 3.0一起使用。
17.1.1.配置选项
mongo-java-driver已更改或删除了某些配置选项。使用第3代驱动程序时,将忽略以下选项:
- 
autoConnectRetry - 
maxAutoConnectRetryTime - 
slaveOk 
通常,在执行基于XML的配置时,应使用<mongo:mongo-client … />和<mongo:client-options … />元素而不是<mongo:mongo … />,因为这些元素为您提供仅对第三代Java驱动程序有效的属性。下面的示例显示了如何配置Mongo客户端连接:
<?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 http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
		http://www.springframework.org/schema/beans http://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>
17.1.2.WriteConcern和WriteConcernChecking
WriteConcern.NONE已被Spring Data MongoDB用作默认值,已在3.0中删除。因此,在MongoDB 3环境中,WriteConcern默认为WriteConcern.UNACKNOWLEGED。如果启用WriteResultChecking.EXCEPTION,则WriteConcern将更改为WriteConcern.ACKNOWLEDGED以进行写入操作。否则,执行期间的错误将无法正确抛出,因为驱动程序不会引发错误。
17.1.3.认证
MongoDB Server第3代在连接到DB时更改了身份验证模型。因此,一些可用于身份验证的配置选项不再有效。在使用MongoCredential设置凭据时,应使用MongoClient特定选项来提供身份验证数据,如以下示例所示:
@Configuration
public class ApplicationContextEventTestsAppConfig extends AbstractMongoConfiguration {
  @Override
  public String getDatabaseName() {
    return "database";
  }
  @Override
  @Bean
  public Mongo mongo() throws Exception {
    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 http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <mongo:mongo-client credentials="user:password@database" />
</beans>
17.1.4.其他细节
本节简要介绍了使用3.0驱动程序时要记住的其他事项:
- 
IndexOperations.resetIndexCache()不再受支持。 - 
任何
MapReduceOptions.extraOption都会被默默忽略。 - 
WriteResult不再保留错误信息,而是抛出Exception。 - 
MongoOperations.executeInSession(…)不再致电requestStart和requestDone。 - 
索引名称生成已成为驱动程序内部操作。Spring Data MongoDB仍然使用2.x模式生成名称。
 - 
第2代和第3代服务器之间以及MMap.v1和WiredTiger存储引擎之间的一些
Exception消息不同。 
附录
附录A:命名空间参考
<repositories />元素
<repositories />元素触发Spring Data存储库基础结构的设置。最重要的属性是base-package,它定义了要扫描Spring Data存储库接口的包。请参阅“ XML配置 ”。下表描述了<repositories />元素的属性:
| 名称 | 描述 | 
|---|---|
  | 
定义要扫描的包,以便在自动检测模式下扩展  | 
  | 
定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选。默认为  | 
  | 
确定用于创建查找程序查询的策略。有关详细信息,请参阅“ 查询查找策略 ”。默认为  | 
  | 
定义搜索包含外部定义查询的Properties文件的位置。  | 
  | 
是否应考虑嵌套存储库接口定义。默认为  | 
附录B:Populators命名空间参考
<populator />元素
<populator />元素允许通过Spring Data存储库基础结构填充数据存储。[ 2 ]
| 名称 | 描述 | 
|---|---|
  | 
从哪里可以找到要从存储库中读取对象的文件。  | 
附录C:Repository查询关键字
支持的查询关键字
下表列出了Spring Data存储库查询派生机制通常支持的关键字。但是,请参阅特定于商店的文档以获取支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。
| 逻辑关键字 | 关键字表达式 | 
|---|---|
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
  | 
附录D:Repository查询返回类型
支持的查询返回类型
下表列出了Spring Data repositories通常支持的返回类型。但是,请查阅特定于商店的文档以获取支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。
地理空间类型(例如GeoResult,GeoResults和GeoPage)仅适用于支持地理空间查询的数据存储。
 | 
| 返回类型 | 描述 | 
|---|---|
  | 
表示没有返回值。  | 
基元  | 
Java原语。  | 
包装类型  | 
Java包装器类型。  | 
  | 
一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回  | 
  | 
  | 
  | 
A   | 
  | 
A   | 
  | 
Java 8或Guava   | 
  | 
Scala或Javaslang   | 
  | 
Java 8   | 
  | 
A   | 
  | 
Java 8   | 
  | 
A   | 
  | 
一大块数据,指示是否有更多可用数据。需要  | 
  | 
带有附加信息的  | 
  | 
带有附加信息的结果条目,例如到参考位置的距离。  | 
  | 
带有附加信息的  | 
  | 
  | 
  | 
项目Reactor   | 
  | 
项目Reactor   | 
  | 
RxJava   | 
  | 
RxJava   | 
  | 
RxJava   |