© 2013-2022 原作者。

本文档中的内容来源于 spring.io ,原始版权归属于 spring.io。由全栈行动派进行翻译、整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。
所有已翻译文档请访问全栈工具箱

前言

Spring Data Elasticsearch 项目将核心 Spring 概念应用于使用 Elasticsearch 搜索引擎的解决方案开发。它提供:

  • Templates 作为存储、搜索、排序文档和构建聚合的高级抽象。

  • Repositories 例如,它使用户能够通过定义具有自定义方法名称的接口来表达查询(有关存储库的基本信息,请参阅 使用 Spring Data Repository )。

您会注意到与 Spring 框架中的 Spring data solr 和 mongodb 支持的相似之处。

1. 新增功能

1.1. Spring Data Elasticsearch 5.1 中的新功能

  • 升级到 Elasticsearch 8.7.0

  • 连接到 Elasticsearch 8 集群时允许指定 TLS 证书

1.2. Spring Data Elasticsearch 5.0 的新功能

  • 升级到 Java 17 基线

  • 升级到 Spring Framework 6

  • 升级到 Elasticsearch 8.5.0

  • 使用新的 Elasticsearch 客户端库

1.3. Spring Data Elasticsearch 4.4 中的新功能

  • 使用来自新 Elasticsearch Java 客户端的类引入新的命令式和响应式客户端

  • 升级到 Elasticsearch 7.17.3.

1.4. Spring Data Elasticsearch 4.3 中的新功能

  • 升级到 Elasticsearch 7.15.2.

  • 允许在索引映射中定义 runtime_fields。

  • 使用范围对象添加对范围字段类型的原生支持。

  • 添加可空或空属性的存储库搜索。

  • 为单个字段启用自定义转换器。

  • 提供自定义 Sort.Order 以提供 Elasticsearch 特定参数。

1.5. Spring Data Elasticsearch 4.2 中的新功能

  • 升级到 Elasticsearch 7.10.0.

  • 支持自定义路由值

1.6. Spring Data Elasticsearch 4.1 中的新功能

  • 使用Spring 5.3.

  • 升级到 Elasticsearch 7.9.3.

  • 改进了用于别名管理的 API。

  • 引入 ReactiveIndexOperations 进行索引管理。

  • 索引模板支持。

  • 使用 GeoJson 支持 Geo-shape 数据。

1.7. Spring Data Elasticsearch 4.0 的新功能

  • 使用Spring 5.2.

  • 升级到 Elasticsearch 7.6.2.

  • 弃用 TransportClient 用法。

  • 实现大多数可用于索引映射的映射类型。

  • 移除 Jackson ObjectMapper,现在使用 MappingElasticsearchConverter

  • 清理 *Operations 接口中的 API,对方法进行分组和重命名,以便它们与 Elasticsearch API 匹配,弃用旧方法,与其他 Spring Data 模块保持一致。

  • 引入 SearchHit<T> 类来表示找到的文档以及该文档的相关结果元数据(即 sortValues )。

  • 引入 SearchHits<T> 类来表示整个搜索结果以及完整搜索结果的元数据(即 max_score )。

  • 引入 SearchPage<T> 类来表示包含 SearchHits<T> 实例的分页结果。

  • 引入 GeoDistanceOrder 类以创建按地理距离排序

  • 实现审计支持

  • 生命周期实体回调的实现

1.8. Spring Data Elasticsearch 3.2 中的新功能

3.要求

需要安装 Elasticsearch

3.1.版本

下表显示了 Spring Data 发布序列使用的 Elasticsearch 版本和其中包含的 Spring Data Elasticsearch 版本,以及引用该特定 Spring Data 发布序列的 Spring Boot 版本。给出的 Elasticsearch 版本显示了构建和测试 Spring Data Elasticsearch 的客户端库。

Spring Data版本系列 Spring Data Elasticsearch Elasticsearch Srping 框架 Spring Boot

2023.0 (Ullmann)

5.1.x

8.7.0

6.0.x

3.1.x

2022.0 (Turing)

5.0.x

8.5.3

6.0.x

3.0.x

2021.2 (Raj)

4.4.x[1]

7.17.3

5.3.x

2.7.x

2021.1 (Q)

4.3.x[1]

7.15.2

5.3.x

2.6.x

2021.0 (Pascal)

4.2.x[1]

7.12.0

5.3.x

2.5.x

2020.0 (Ockham)

4.1.x[1]

7.9.3

5.3.2

2.4.x

Neumann

4.0.x[1]

7.6.2

5.2.12

2.3.x

Moore

3.2.x[1]

6.8.12

5.2.12

2.2.x

Lovelace

3.1.x[1]

6.2.2

5.1.19

2.1.x

Kay

3.0.x[1]

5.5.0

5.0.13

2.0.x

Ingalls

2.1.x[1]

2.4.0

4.3.25

1.5.x

正在跟踪对即将推出的 Elasticsearch 版本的支持,假设使用 ElasticsearchOperations 接口 ,应该给出一般兼容性。 :leveloffset:+1

升级 Spring Data

维基 项目提供了有关如何从早期版本的 Spring Data 升级的说明。按照 发行说明部分 中的链接找到您要升级到的版本。

升级说明始终是发行说明中的第一项。如果您落后不止一个版本,请确保您还查看了您跳转的版本的发行说明。

4. 使用 Spring Data Repository

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

Spring Data repository documentation and your module

本章解释了 Spring Data 存储库的核心概念和接口。本章中的信息来自 Spring Data Commons 模块。它使用 Jakarta Persistence API (JPA) 模块的配置和代码样本。如果你想使用 XML 配置,你应该调整 XML 命名空间声明和要扩展的类型,以适应你使用的特定模块的等价物。 “命名空间参考”涵盖了 XML 配置,所有支持存储库 API 的 Spring Data 模块都支持该配置。 “Repository查询关键字”涵盖了一般存储库抽象支持的查询方法关键字。有关模块特定功能的详细信息,请参阅本文档中有关该模块的章节。

4.1.核心概念

Spring Data 存储库抽象中的中央接口是 Repository 。它需要管理域类以及域类的标识符类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。 CrudRepository _n1178_ 接口为被管理的实体类提供复杂的 CRUD 功能。

示例 1.CrudRepository 接口
public interface CrudRepository<T, ID> extends Repository<T, ID> {

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

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

 Iterable<T> findAll();        (3)

 long count();            (4)

 void delete(T entity);        (5)

 boolean existsById(ID primaryKey);  (6)

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

在此接口中声明的方法通常称为 CRUD 方法。 ListCrudRepository 提供等效方法,但它们返回 List,其中 CrudRepository 方法返回 Iterable

我们还提供持久化技术特定的抽象,例如 JpaRepositoryMongoRepository 。这些接口扩展了 CrudRepository 并公开了底层持久化技术的功能,以及相当通用的持久化技术不可知的接口,例如 CrudRepository

除了 CrudRepository 之外,还有一个 PagingAndSortingRepository 抽象,它添加了额外的方法来简化对实体的分页访问:

示例 2.PagingAndSortingRepository 接口
public interface PagingAndSortingRepository<T, ID> {

 Iterable<T> findAll(Sort sort);

 Page<T> findAll(Pageable pageable);
}
 

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

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

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

示例 3. 派生计数查询
interface UserRepository extends CrudRepository<User, Long> {

 long countByLastname(String lastname);
}
 

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

示例 4. 派生删除查询
interface UserRepository extends CrudRepository<User, Long> {

 long deleteByLastname(String lastname);

 List<User> removeByLastname(String lastname);
}
 

4.2.查询方法

标准 CRUD 功能存储库通常对底层数据存储有查询。使用 Spring Data,声明这些查询变成一个四步过程:

  1. 声明一个扩展 Repository 的接口或其子接口之一,并将其键入它应处理的域类和 ID 类型,如以下示例所示:

    interface PersonRepository extends Repository<Person, Long> { … }
     
  2. 在接口上声明查询方法。

    interface PersonRepository extends Repository<Person, Long> {
     List<Person> findByLastname(String lastname);
    }
     
  3. 设置 Spring 以使用 JavaConfig 或使用 XML configuration 为这些接口创建代理实例。

    Java
    XML
    import org.springframework.data.….repository.config.EnableJpaRepositories;
    
    @EnableJpaRepositories
    class Config { … }
     

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

    请注意,JavaConfig 变体不会显式配置包,因为默认使用注解类的包。要自定义要扫描的包,请使用数据存储特定存储库的 @EnableJpaRepositories 注解的 basePackage… 属性之一。

  4. 注入存储库实例并使用它,如以下示例所示:

    class SomeClient {
    
     private final PersonRepository repository;
    
     SomeClient(PersonRepository repository) {
      this.repository = repository;
     }
    
     void doSomething() {
      List<Person> persons = repository.findByLastname("Matthews");
     }
    }
     

以下部分详细解释了每个步骤:

4.3.定义存储库接口

要定义存储库接口,您首先需要定义一个领域类特定的存储库接口。该接口必须扩展 Repository 并且类型化为域类和 ID 类型。如果您想公开该域类型的 CRUD 方法,您可以扩展 CrudRepository 或其变体之一而不是 Repository

4.3.1.微调存储库定义

您可以通过多种方式开始使用存储库接口。

典型的方法是扩展 CrudRepository ,它为您提供了 CRUD 功能的方法。 CRUD 代表创建、读取、更新、删除。在 3.0 版中,我们还引入了 ListCrudRepository,它与 CrudRepository 非常相似,但对于那些返回多个实体的方法,它返回 List 而不是 Iterable,您可能会发现后者更易于使用。

如果您使用的是响应式存储,您可以选择 ReactiveCrudRepositoryRxJava3CrudRepository,具体取决于您使用的响应式框架。

如果您使用的是 Kotlin,您可能会选择 CoroutineCrudRepository,它利用了 Kotlin 的协程。

另外,如果您需要允许指定 Sort 抽象或在第一种情况下为 Pageable 抽象的方法,则可以扩展 PagingAndSortingRepositoryReactiveSortingRepositoryRxJava3SortingRepositoryCoroutineSortingRepository。请注意,各种排序存储库不再像在 Spring Data 3.0 之前的版本中那样扩展其各自的 CRUD 存储库。因此,如果您需要两者的功能,则需要扩展这两个接口。

如果您不想扩展 Spring Data 接口,您也可以使用 @RepositoryDefinition 注解您的存储库接口。扩展其中一个 CRUD 存储库接口会公开一组完整的方法来操作您的实体。如果您希望对公开的方法有选择性,请将要公开的方法从 CRUD 存储库复制到您的域存储库中。这样做时,您可以更改方法的返回类型。如果可能,Spring Data 将遵循返回类型。例如,对于返回多个实体的方法,您可以选择 Iterable<T>List<T>Collection<T> 或 VAVR 列表。

如果您的应用程序中的许多存储库应该具有相同的一组方法,您可以定义自己的基接口来继承。这样的接口必须用 @NoRepositoryBean 注解。这可以防止 Spring Data 尝试直接创建它的实例并失败,因为它无法确定该存储库的实体,因为它仍然包含一个通用类型变量。

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

示例 5. 有选择地公开 CRUD 方法
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

 Optional<T> findById(ID id);

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

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

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

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

4.3.2.将存储库与多个 Spring Data模块一起使用

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

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

  2. 如果域类是 用特定于模块的类型注解进行注解 ,那么它是特定 Spring Data 模块的有效候选者。 Spring Data 模块接受第三方注解(例如 JPA 的 @Entity )或提供自己的注解(例如 @Document 用于 Spring Data MongoDB 和 Spring Data Elasticsearch)。

以下示例显示了一个使用模块特定接口(在本例中为 JPA)的存储库:

示例 6. 使用特定于模块的接口的存储库定义
interface MyRepository extends JpaRepository<User, Long> { }

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

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

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

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

示例 7. 使用通用接口的存储库定义
interface AmbiguousRepository extends Repository<User, Long> { … }

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

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

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

以下示例显示了一个使用带注解的域类的存储库:

示例 8. 使用带注解的域类的存储库定义
interface PersonRepository extends Repository<Person, Long> { … }

@Entity
class Person { … }

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

@Document
class User { … }
 

PersonRepository 引用 Person ,它使用 JPA @Entity 注解进行注解,因此此存储库显然属于 Spring Data JPA。 UserRepository 引用 User ,它使用 Spring Data MongoDB 的 @Document 注解进行注解。

以下错误示例显示了一个使用带有混合注解的域类的存储库:

示例 9. 使用带有混合注解的域类的存储库定义
interface JpaPersonRepository extends Repository<Person, Long> { … }

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

@Entity
@Document
class Person { … }
 

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

Repository类型详细信息distinguishing domain class annotations 用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。在同一域类型上使用多个持久化技术特定的注解是可能的,并且可以跨多个持久化技术重用域类型。但是,Spring Data 无法再确定用于绑定存储库的唯一模块。

区分存储库的最后一种方法是确定存储库基础包的范围。基础包定义了扫描存储库接口定义的起点,这意味着存储库定义位于适当的包中。默认情况下,注解驱动配置使用配置类的包。 基于 XML 的配置中的基础包 是强制性的。

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

示例 10. 基础包的注解驱动配置
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
 

4.4.定义查询方法

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

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

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

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

4.4.1.查询查找策略

以下策略可用于存储库基础结构来解析查询。使用 XML 配置,您可以通过 query-lookup-strategy 属性在命名空间配置策略。对于 Java 配置,您可以使用 EnableJpaRepositories 注解的 queryLookupStrategy 属性。特定数据存储可能不支持某些策略。

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

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

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

4.4.2.查询创建

Spring Data 存储库基础结构中内置的查询构建器机制对于构建存储库实体的约束查询很有用。

以下示例显示了如何创建多个查询:

示例 11. 从方法名称创建查询
interface PersonRepository extends Repository<Person, Long> {

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

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

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

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

解析查询方法名称分为主语和谓语。第一部分 (find…By , exists…By ) 定义查询的主题,第二部分构成谓词。介绍子句(主语)可以包含更多的表达。 find(或其他引入关键字)和 By 之间的任何文本都被认为是描述性的,除非使用结果限制关键字之一,例如 Distinct 在要创建的查询或 Top /_n1319_ 限制查询结果 上设置不同的标志。

附录包含 查询方法主题关键字的完整列表query method predicate keywords including sorting and letter-casing modifiers 。但是,第一个 By 充当分隔符以指示实际条件谓词的开始。在非常基本的层面上,您可以定义实体属性的条件并将它们与 AndOr 连接起来。

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

  • 这些表达式通常是属性遍历与可以连接的运算符相结合。您可以将属性表达式与 ANDOR 结合使用。您还可以获得对属性表达式的 BetweenLessThanGreaterThanLike 等运算符的支持。支持的运算符可能因数据存储而异,因此请查阅参考文档的相应部分。

  • 方法解析器支持为单个属性(例如 findByLastnameIgnoreCase(…) )或支持忽略大小写的类型的所有属性(通常是 String 实例——例如 findByLastnameAndFirstnameAllIgnoreCase(…) )设置一个 IgnoreCase 标志。是否支持忽略大小写可能因存储而异,具体存储的查询方式请查阅参考文档中的相关章节。

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

4.4.3.属性表达式

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

List<Person> findByAddressZipCode(ZipCode zipCode);
 

假设 Person 有一个 Address 和一个 ZipCode。在这种情况下,该方法会创建 x.address.zipCode 属性遍历。解析算法首先将整个部分 (AddressZipCode) 解释为属性,并检查域类中是否有具有该名称(未大写)的属性。如果算法成功,它将使用该属性。如果不是,该算法会将源代码从右侧的驼峰式部分拆分为头部和尾部,并尝试找到相应的属性——在我们的示例中为 AddressZipCode 。如果该算法找到具有该头部的属性,它会获取尾部并继续从那里向下构建树,以刚才描述的方式拆分尾部。如果第一次分割不匹配,算法将分割点向左移动(AddressZipCode)并继续。

虽然这适用于大多数情况,但算法可能会选择错误的属性。假设 Person 类也有一个 addressZip 属性。该算法将在第一轮拆分中匹配,选择错误的属性,然后失败(因为 addressZip 的类型可能没有 code 属性)。

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

List<Person> findByAddress_ZipCode(ZipCode zipCode);
 

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

4.4.4.分页、迭代大结果、排序

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

示例 12. 在查询方法中使用 PageableSliceSort
Page<User> findByLastname(String lastname, Pageable pageable);

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

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

List<User> findByLastname(String lastname, Pageable pageable);
 
采用 SortPageable 的 API 期望将非 null 值传递给方法。如果您不想应用任何排序或分页,请使用 Sort.unsorted()Pageable.unpaged()

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

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

要了解您为整个查询获得了多少页,您必须触发额外的计数查询。默认情况下,此查询派生自您实际触发的查询。
哪种方法合适?

下表中概述的可能的查询方法返回类型可能最好地显示了 Spring Data 抽象提供的值。该表显示了您可以从查询方法返回哪些类型

表 1. 使用大型查询结果
方法 获取的数据量 查询结构 约束条件

List<T>

所有结果。

单一查询。

查询结果可能会耗尽所有内存。获取所有数据可能非常耗时。

Streamable<T>

所有结果。

单一查询。

查询结果可能会耗尽所有内存。获取所有数据可能非常耗时。

Stream<T>

根据 Stream 消耗量分块(一个接一个或分批)。

通常使用游标的单个查询。

流必须在使用后关闭以避免资源泄漏。

Flux<T>

根据 Flux 消耗量分块(一个接一个或分批)。

通常使用游标的单个查询。

Store 模块必须提供响应式基础设施。

CloseableIterator<T>

根据 CloseableIterator 消耗量分块(一个接一个或分批)。

通常使用游标的单个查询。

CloseableIterator 在使用后需要关闭以避免资源泄漏。

Slice<T>

Pageable.getPageSize() + 1Pageable.getOffset()

一对多查询从 Pageable.getOffset() 开始获取数据并应用限制。

一个 Slice 只能导航到下一个 Slice

  • Slice 提供了是否有更多数据要获取的详细信息。

  • 当偏移量太大时,基于偏移量的查询变得低效,因为数据库仍然必须具体化完整的结果。

  • Window 提供了是否有更多数据要获取的详细信息。

  • 当偏移量太大时,基于偏移量的查询变得低效,因为数据库仍然必须具体化完整的结果。

Page<T>

Pageable.getPageSize()Pageable.getOffset()

Pageable.getOffset() 开始的一对多查询应用限制。此外,可能需要 COUNT(…) 查询以确定元素总数。

很多时候,COUNT(…) 查询是非常昂贵的。

  • 当偏移量太大时,基于偏移量的查询变得低效,因为数据库仍然必须具体化完整的结果。

分页和排序

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

示例 13. 定义排序表达式
Sort sort = Sort.by("firstname").ascending()
 .and(Sort.by("lastname").descending());
 

要以一种类型更安全的方式来定义排序表达式,请从要为其定义排序表达式的类型开始,然后使用方法引用来定义要排序的属性。

示例 14. 使用类型安全 API 定义排序表达式
TypedSort<Person> person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()
 .and(person.by(Person::getLastname).descending());
 
TypedSort.by(…) 通过(通常)使用 CGlib 使用运行时代理,这可能会在使用 Graal VM Native 等工具时干扰原生镜像编译。

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

示例 15. 使用 Querydsl API 定义排序表达式
QSort sort = QSort.by(QPerson.firstname.asc())
 .and(QSort.by(QPerson.lastname.desc()));
 

4.4.5.限制查询结果

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

示例 16. 使用 TopFirst 限制查询的结果大小
User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

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

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

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

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

对于支持不同查询的数据存储,限制表达式还支持 Distinct 关键字。此外,对于将结果集限制为一个实例的查询,支持使用Optional 关键字将结果包装成。

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

通过使用 Sort 参数限制结果与动态排序相结合,您可以表达“K”最小元素和“K”最大元素的查询方法。

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

返回多个结果的查询方法可以使用标准 Java IterableListSet。除此之外,我们支持返回 Spring Data 的 StreamableIterable 的自定义扩展,以及 Vavr 提供的集合类型。请参阅解释所有可能的 查询方法返回类型 的附录。

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

您可以使用 Streamable 作为 Iterable 或任何集合类型的替代。它提供了方便的方法来访问非并行 Stream(从 Iterable 中丢失)以及直接 ….filter(…)….map(…) 元素并将 Streamable 连接到其他元素的能力:

示例 17. 使用 Streamable 组合查询方法结果
interface PersonRepository extends Repository<Person, Long> {
 Streamable<Person> findByFirstnameContaining(String firstname);
 Streamable<Person> findByLastnameContaining(String lastname);
}

Streamable<Person> result = repository.findByFirstnameContaining("av")
 .and(repository.findByLastnameContaining("ea"));
 
返回自定Streamable Wrapper类型

为集合提供专用包装器类型是一种常用模式,可为返回多个元素的查询结果提供 API。通常,通过调用存储库方法返回类集合类型并手动创建包装类型的实例来使用这些类型。您可以避免该额外步骤,因为 Spring Data 允许您将这些包装器类型用作查询方法返回类型,前提是它们满足以下条件:

  1. 该类型实现了 Streamable

  2. 该类型公开了一个构造函数或一个名为 of(…)valueOf(…) 的静态工厂方法,它以 Streamable 作为参数。

以下清单显示了一个示例:

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

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

 private final Streamable<Product> streamable;

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


 @Override
 public Iterator<Product> iterator() {         (4)
  return streamable.iterator();
 }
}

interface ProductRepository implements Repository<Product, Long> {
 Products findAllByDescriptionContaining(String text); (5)
}
 
1 公开 API 以访问产品价格的 Product 实体。
2 Streamable<Product> 的包装器类型,可以使用 Products.of(…)(使用 Lombok 注解创建的工厂方法)构造。采用 Streamable<Product> 的标准构造函数也可以。
3 包装器类型公开了一个额外的 API,用于计算 Streamable<Product> 上的新值。
4 实现 Streamable 接口并委托给实际结果。
5 该包装器类型 Products 可以直接用作查询方法返回类型。您不需要在存储库客户端中查询后返回 Streamable<Product> 并手动包装它。
支持 Vavr 集合

Vavr 是一个包含 Java 函数式编程概念的库。它附带一组自定义集合类型,您可以将其用作查询方法返回类型,如下表所示:

Vavr 集合类型 使用的 Vavr 实现类型 有效的 Java 源类型

io.vavr.collection.Seq

io.vavr.collection.List

java.util.Iterable

io.vavr.collection.Set

io.vavr.collection.LinkedHashSet

java.util.Iterable

io.vavr.collection.Map

io.vavr.collection.LinkedHashMap

java.util.Map

您可以将第一列中的类型(或其子类型)用作查询方法返回类型,并将第二列中的类型用作实现类型,具体取决于实际查询结果(第三列)的 Java 类型。或者,您可以声明 Traversable(相当于 Vavr Iterable),然后我们从实际返回值派生实现类。也就是说,java.util.List 变成 Vavr ListSeqjava.util.Set 变成 Vavr LinkedHashSet Set,等等。

4.4.7.流式查询结果

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

示例 18. 使用 Java 8 Stream<T> 流式传输查询结果
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
 
Stream 可能包含底层数据存储特定的资源,因此必须在使用后关闭。您可以使用 close() 方法或使用 Java 7 try-with-resources 块手动关闭 Stream,如以下示例所示:
示例 19. 在 try-with-resources 块中使用 Stream<T> 结果
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
 stream.forEach(…);
}
 
并非所有 Spring Data 模块当前都支持 Stream<T> 作为返回类型。

4.4.8.存储库方法的空处理

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

  • com.google.common.base.Optional

  • scala.Option

  • io.vavr.control.Option

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

可空性注解

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

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

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

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

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

示例 20. 在 package-info.java 中声明不可空性
@org.springframework.lang.NonNullApi
package com.acme;
 

一旦非空默认设置到位,存储库查询方法调用将在运行时针对可空性约束进行验证。如果查询结果违反定义的约束,则抛出异常。当该方法将返回 null 但被声明为不可为空(默认情况下,在存储库所在的包上定义了注解)时会发生这种情况。如果您想再次选择加入可为空的结果,请在各个方法上有选择地使用 @Nullable。使用本节开头提到的结果包装器类型继续按预期工作:空结果被转换为表示不存在的值。

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

示例 21. 使用不同的可空性约束
package com.acme;                            (1)

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

 User getByEmailAddress(EmailAddress emailAddress);          (2)

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

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

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

示例 22. 在 Kotlin 存储库上使用可空性约束
interface UserRepository : Repository<User, String> {

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

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

4.4.9.异步查询结果

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

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

@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
 
1 使用 java.util.concurrent.Future 作为返回类型。
2 使用 Java 8 java.util.concurrent.CompletableFuture 作为返回类型。

4.5.创建存储库实例

本节介绍如何为定义的存储库接口创建实例和 bean 定义。

4.5.1. Java配置

在 Java 配置类上使用特定于存储的 @EnableJpaRepositories 注解来定义存储库激活的配置。有关 Spring 容器的基于 Java 的配置的介绍,请参阅 Spring 参考文档中的 JavaConfig

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

示例 23. 基于注解的存储库配置示例
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

 @Bean
 EntityManagerFactory entityManagerFactory() {
  // …
 }
}
 
前面的示例使用特定于 JPA 的注解,您可以根据实际使用的存储模块更改它。这同样适用于 EntityManagerFactory bean 的定义。请参阅涵盖存储特定配置的部分。

4.5.2. XML配置

每个 Spring Data 模块都包含一个 repositories 元素,它允许您定义 Spring 为您扫描的基础包,如以下示例所示:

示例 24. 通过 XML 启用 Spring Data Repository
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://www.springframework.org/schema/data/jpa"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/data/jpa
  https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

</beans:beans> 

在前面的示例中,指示 Spring 扫描 com.acme.repositories 及其所有子包以查找扩展 Repository 或其子接口之一的接口。对于找到的每个接口,基础schema都会注册特定于持久化技术的FactoryBean,以创建适当的代理来处理查询方法的调用。每个 bean 都在派生自接口名称的 bean 名称下注册,因此 UserRepository 的接口将在 userRepository 下注册。嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。基本包属性允许使用通配符,以便您可以定义扫描包的模式。

4.5.3.使用过滤器

默认情况下,基础schema会选择每个扩展持久化技术特定的 Repository 子接口的接口,该子接口位于已配置的基础包下,并为其创建一个 bean 实例。但是,您可能希望对哪些接口具有为其创建的 bean 实例进行更细粒度的控制。为此,请在存储库声明中使用过滤器元素。语义完全等同于 Spring 的组件过滤器中的元素。有关详细信息,请参阅这些元素的 Spring参考文档

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

例子 25. 使用过滤器
Java
XML
@Configuration
@EnableJpaRepositories(basePackages = "com.acme.repositories",
  includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeRepository") },
  excludeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeOtherRepository") })
class ApplicationConfiguration {

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

前面的示例排除了所有以 SomeRepository 结尾的接口被实例化,并包括以 SomeOtherRepository 结尾的接口。

4.5.4.独立使用

您还可以在 Spring 容器之外使用存储库基础设施——例如,在 CDI 环境中。您的类路径中仍然需要一些 Spring 库,但通常,您也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了一个特定于持久化技术的RepositoryFactory,您可以使用它,如下所示:

示例 26. 存储库工厂的独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
 

4.6. Spring Data Repository的自定义实现

Spring Data 提供了多种选项来创建几乎没有编码的查询方法。但是当这些选项不符合您的需要时,您还可以为存储库方法提供您自己的自定义实现。本节描述如何做到这一点。

4.6.1.自定义单个Repository

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

示例 27. 自定义存储库功能的接口
interface CustomizedUserRepository {
 void someCustomMethod(User user);
}
 
示例 28. 自定义存储库功能的实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

 public void someCustomMethod(User user) {
  // Your custom implementation
 }
}
 
与片段接口对应的类名中最重要的部分是Impl 后缀。

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

然后你可以让你的存储库接口扩展片段接口,如下所示:

示例 29. 对存储库接口的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

 // Declare query methods here
}
 

使用您的存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。

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

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

示例 30. 片段及其实现
interface HumanRepository {
 void someHumanMethod(User user);
}

class HumanRepositoryImpl implements HumanRepository {

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

interface ContactRepository {

 void someContactMethod(User user);

 User anotherContactMethod(User user);
}

class ContactRepositoryImpl implements ContactRepository {

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

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

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

示例 31. 对存储库接口的更改
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

 // Declare query methods here
}
 

存储库可能由多个自定义实现组成,这些实现按声明的顺序导入。自定义实现比基本实现和存储库方面具有更高的优先级。如果两个片段提供相同的方法签名,此排序可让您覆盖基本存储库和方面方法并解决歧义。存储库片段不限于在单个Repository接口中使用。多个存储库可以使用片段接口,让您可以跨不同的存储库重用自定义。

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

示例 32. 覆盖 save(…) 的片段
interface CustomizedSave<T> {
 <S extends T> S save(S entity);
}

class CustomizedSaveImpl<T> implements CustomizedSave<T> {

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

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

例子 33.自定义的存储库接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

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

存储库基础结构尝试通过扫描它在其中找到存储库的包下的类来自动检测自定义实现片段。这些类需要遵循附加后缀默认为 Impl 的命名约定。

以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:

示例 34. 配置示例
Java
XML
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
 

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

歧义的解决

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

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

示例 35. 解决不明确的实现
package com.acme.impl.one;

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

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

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

 // Your custom implementation
}
 

如果用 @Component("specialCustom") 注解 UserRepository 接口,则 bean 名称加上 Impl 与为存储库实现在 com.acme.impl.two 中定义的那个相匹配,并且使用它而不是第一个。

手动接线

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

示例 36.自定义实现的手动连接
Java
XML
class MyClass {
 MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
  …
 }
}
 

4.6.2.自定义基本存储库

上一节 中描述的方法需要在您想要自定义基本存储库行为时自定义每个存储库接口,以便影响所有存储库。要改为更改所有存储库的行为,您可以创建一个扩展特定于持久化技术的存储库基类的实现。然后,此类充当存储库代理的自定义基类,如以下示例所示:

示例 37. 自定义存储库基类
class MyRepositoryImpl<T, ID>
 extends SimpleJpaRepository<T, ID> {

 private final EntityManager entityManager;

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

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

 @Transactional
 public <S extends T> S save(S entity) {
  // implementation goes here
 }
}
 
该类需要具有特定于存储的存储库工厂实现使用的超类的构造函数。如果存储库基类有多个构造函数,请覆盖采用 EntityInformation 加上存储特定基础结构对象(例如 EntityManager 或模板类)的构造函数。

最后一步是让 Spring Data 基础设施知道自定义的存储库基类。在配置中,您可以使用 repositoryBaseClass 来执行此操作,如以下示例所示:

示例 38.配置自定义存储库基类
Java
XML
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
 

4.7.从聚合根发布事件

存储库管理的实体是聚合根。在领域驱动设计应用程序中,这些聚合根通常会发布领域事件。 Spring Data 提供了一个名为 @DomainEvents 的注解,您可以在聚合根的方法上使用它来使该发布尽可能简单,如以下示例所示:

示例 39. 从聚合根公开域事件
class AnAggregateRoot {

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

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

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

4.8.Spring Data扩展

本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。目前,大部分集成都是针对 Spring MVC 的。

4.8.1.查询扩展

Querydsl 是一个框架,可以通过其Fluent API 构建静态类型的 SQL 类查询。

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

例子 40.QuerydslPredicateExecutor 接口
public interface QuerydslPredicateExecutor<T> {

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

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

 long count(Predicate predicate);      (3)

 boolean exists(Predicate predicate);    (4)

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

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

示例 41. 存储库上的 Querydsl 集成
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
 

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

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

userRepository.findAll(predicate);
 

4.8.2.Web 支持

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

示例 42.启用 Spring Data Web 支持
Java
XML
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
 

@EnableSpringDataWebSupport 注解注册了一些组件。我们将在本节后面讨论这些内容。它还在类路径上检测 Spring HATEOAS 并为其注册集成组件(如果存在)。

基本Web 支持
在 XML 中启用 Spring Data Web 支持

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

使用 DomainClassConverter

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

示例 43. 在方法签名中使用域类型的 Spring MVC 控制器
@Controller
@RequestMapping("/users")
class UserController {

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

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

该方法直接接收 User 实例,无需进一步查找。解析实例的方法是让Spring MVC先将路径变量转换为领域类的id类型,最终通过调用为领域类型注册的repository实例上的findById(…)来访问实例。

目前,存储库必须实现 CrudRepository 才有资格被发现进行转换。
用于分页和排序的 HandlerMethodArgumentResolvers

上一节 中显示的配置片段还注册了 PageableHandlerMethodArgumentResolver 以及 SortHandlerMethodArgumentResolver 的实例。注册使 PageableSort 成为有效的控制器方法参数,如以下示例所示:

示例 44. 使用 Pageable 作为控制器方法参数
@Controller
@RequestMapping("/users")
class UserController {

 private final UserRepository repository;

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

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

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

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

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

page

您要检索的页面。 0 索引,默认为 0.

size

您要检索的页面的大小。默认为 20.

sort

应按 property,property(,ASC|DESC)(,IgnoreCase) 格式排序的属性。默认排序方向是区分大小写的升序。如果要切换方向或区分大小写,请使用多个 sort 参数 - 例如, ?sort=firstname&sort=lastname,asc&sort=city,ignorecase

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

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

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

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

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

您必须填充 thing1_pagething2_page 等等。

传入方法的默认 Pageable 等同于 PageRequest.of(0, 20) ,但您可以使用 Pageable 参数上的 @PageableDefault 注解对其进行自定义。

PageSlice 超媒体支持

Spring HATEOAS 附带一个表示模型类 (PagedModel /SlicedModel ),它允许使用必要的 Page /Slice 元数据以及链接来丰富 PageSlice 实例的内容,让客户端轻松导航页面。 PagePagedModel 的转换是通过 Spring HATEOAS RepresentationModelAssembler 接口的实现完成的,称为 PagedResourcesAssembler 。同样,可以使用 SlicedResourcesAssemblerSlice 实例转换为 SlicedModel。以下示例显示了如何使用 PagedResourcesAssembler 作为控制器方法参数,因为 SlicedResourcesAssembler 的工作方式完全相同:

示例 45. 使用 PagedResourcesAssembler 作为控制器方法参数
@Controller
class PersonController {

 private final PersonRepository repository;

 // Constructor omitted

 @GetMapping("/people")
 HttpEntity<PagedModel<Person>> people(Pageable pageable,
  PagedResourcesAssembler assembler) {

  Page<Person> people = repository.findAll(pageable);
  return ResponseEntity.ok(assembler.toModel(people));
 }
}
 

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

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

  • PagedModel 对象附加了一个 PageMetadata 实例,并填充了来自 Page 和底层 Pageable 的信息。

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

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

{ "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
 }
} 
此处显示的 JSON 信封格式不遵循任何正式指定的结构,不保证稳定,我们可能会随时更改它。强烈建议将渲染启用为支持超媒体的官方媒体类型,由 Spring HATEOAS 支持,如 HAL 。这些可以通过使用其 @EnableHypermediaSupport 注解来激活。在 Spring HATEOAS 参考文档 中查找更多信息。

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

Spring Data Jackson 模块

核心模块和一些特定于存储的模块附带一组 Jackson 模块,用于 Spring Data 域使用的类型,如 org.springframework.data.geo.Distanceorg.springframework.data.geo.Point
一旦 Web 支持 启用并且 com.fasterxml.jackson.databind.ObjectMapper 可用,这些模块就会被导入。

在初始化期间 SpringDataJacksonModulesSpringDataJacksonConfiguration 一样,被基础设施接收,这样声明的 com.fasterxml.jackson.databind.Module 就可以供 Jackson ObjectMapper 使用。

以下域类型的数据绑定混合由公共基础设施注册。

org.springframework.data.geo.Distance
org.springframework.data.geo.Point
org.springframework.data.geo.Box
org.springframework.data.geo.Circle
org.springframework.data.geo.Polygon

个别模块可能会提供额外的 SpringDataJacksonModules
有关更多详细信息,请参阅存储特定部分。

Web 数据绑定支持

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

示例 46. 使用 JSONPath 或 XPath 表达式的 HTTP 负载绑定
@ProjectedPayload
public interface UserPayload {

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

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

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

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

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

有关详细信息,请参阅规范 Spring Data 示例库 中的 web projection example

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 运行它。

类型信息通常从方法的返回类型中解析出来。由于该信息不一定与域类型匹配,因此使用 QuerydslPredicateroot 属性可能是个好主意。

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

@Controller
class UserController {

 @Autowired UserRepository repository;

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

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

  return "index";
 }
}
 
1 解析查询字符串参数以匹配 Predicate for User

默认绑定如下:

  • Objecteq 一样的简单属性。

  • Object 在像 contains 这样的集合上。

  • Collectionin 一样的简单属性。

您可以通过 @QuerydslPredicatebindings 属性或使用 Java 8 default methods 并将 QuerydslBinderCustomizer 方法添加到存储库接口来自定义这些绑定,如下所示:

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

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

  bindings.bind(user.username).first((path, value) -> path.contains(value))  (3)
  bindings.bind(String.class)
   .first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
  bindings.excluding(user.password);                      (5)
 }
}
 
1 QuerydslPredicateExecutor 提供对 Predicate 特定查找器方法的访问。
2 QuerydslBinderCustomizer 定义在存储库接口上自动拾取和快捷方式 @QuerydslPredicate(bindings=…​)
3 username 属性的绑定定义为简单的 contains 绑定。
4 String 属性的默认绑定定义为不区分大小写的 contains 匹配。
5 Predicate 分辨率中排除 password 属性。
在从存储库或 @QuerydslPredicate 应用特定绑定之前,您可以注册一个持有默认 Querydsl 绑定的 QuerydslBinderCustomizerDefaults bean。

4.8.3.Repository 填充器

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

假设您有一个名为 data.json 的文件,其中包含以下内容:

示例 47. JSON 中定义的数据
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
 "lastname" : "Matthews" },
 { "_class" : "com.acme.Person",
 "firstname" : "Carter",
 "lastname" : "Beauford" } ] 

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

示例 48. 声明 Jackson 存储库填充器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:repository="http://www.springframework.org/schema/data/repository"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/data/repository
  https://www.springframework.org/schema/data/repository/spring-repository.xsd">

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

</beans> 

前面的声明导致 data.json 文件被 Jackson ObjectMapper 读取和反序列化。

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

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

示例 49. 声明一个解组存储库填充器(使用 JAXB)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:repository="http://www.springframework.org/schema/data/repository"
 xmlns:oxm="http://www.springframework.org/schema/oxm"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/data/repository
  https://www.springframework.org/schema/data/repository/spring-repository.xsd
  http://www.springframework.org/schema/oxm
  https://www.springframework.org/schema/oxm/spring-oxm.xsd">

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

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

</beans> 

参考文档

5. Elasticsearch 客户端

本章说明支持的 Elasticsearch 客户端实现的配置和使用。

Spring Data Elasticsearch 在连接到单个 Elasticsearch 节点或集群的 Elasticsearch 客户端(由 Elasticsearch 客户端库提供)上运行。虽然 Elasticsearch Client 可以直接用于集群,但使用 Spring Data Elasticsearch 的应用程序通常使用 Elasticsearch 操作Elasticsearch Repositories 的更高级别抽象。

5.1.命令式 Rest 客户端

要使用命令式(非响应式)客户端,必须像这样配置配置 bean:

import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;

@Configuration
public class MyClientConfig extends ElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder()      (1)
			.connectedTo("localhost:9200")
			.build();
	}
}
 
1 有关构建器方法的详细说明,请参阅 客户端配置

然后可以将以下 bean 注入到其他 Spring 组件中:

@Autowired
ElasticsearchOperations operations;   (1)

@Autowired
ElasticsearchClient elasticsearchClient; (2)

@Autowired
RestClient restClient;          (3)
 
1 ElasticsearchOperations 的实现
2 使用的co.elastic.clients.elasticsearch.ElasticsearchClient
3 来自 Elasticsearch 库的低级 RestClient

基本上应该只使用 ElasticsearchOperations 与 Elasticsearch 集群交互。使用存储库时,此实例也在后台使用。

5.2.响应式Rest 客户端

使用响应式堆时,配置必须从不同的类派生:

import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;

@Configuration
public class MyClientConfig extends ReactiveElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder()      (1)
			.connectedTo("localhost:9200")
			.build();
	}
}
 
1 有关构建器方法的详细说明,请参阅 客户端配置

然后可以将以下 bean 注入到其他 Spring 组件中:

@Autowired
ReactiveElasticsearchOperations operations;   (1)

@Autowired
ReactiveElasticsearchClient elasticsearchClient; (2)

@Autowired
RestClient restClient;              (3)
 

可以注入以下内容:

1 ReactiveElasticsearchOperations 的实现
2 使用的org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient。这是一个基于 Elasticsearch 客户端实现的响应式实现。
3 来自 Elasticsearch 库的低级 RestClient

基本上应该只使用 ReactiveElasticsearchOperations 与 Elasticsearch 集群交互。使用存储库时,此实例也在后台使用。

5.3.高级 REST 客户端(已弃用)

Elasticsearch Java RestHighLevelClient 已弃用,但仍然可以像所示那样进行配置(请务必同时阅读 还想用老客户端?)。

它应该只用于访问运行版本 7 的 Elasticsearch 集群,即使设置了兼容性header,在某些情况下 RestHighLevelClient 也无法读取从版本 8 集群发送的响应。

示例 50.RestHighLevelClient
import org.springframework.data.elasticsearch.client.erhlc.AbstractElasticsearchConfiguration;

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

  @Override
  @Bean
  public RestHighLevelClient elasticsearchClient() {

    final ClientConfiguration clientConfiguration = ClientConfiguration.builder() (1)
      .connectedTo("localhost:9200")
      .build();

    return RestClients.create(clientConfiguration).rest();             (2)
  }
}

// ...

 @Autowired
 RestHighLevelClient highLevelClient;

 RestClient lowLevelClient = highLevelClient.lowLevelClient();            (3)
 
1 使用构建器提供集群地址,设置默认值 HttpHeaders 或启用 SSL。
2 创建 RestHighLevelClient。
3 也可以获得lowLevelRest()客户端。

5.4.响应式客户端(已弃用)

org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient 是基于 WebClient 的非官方驱动程序。它使用 Elasticsearch 核心项目提供的请求/响应对象。调用直接在响应式堆上操作,not将异步(线程池绑定)响应包装到响应式类型中。

这是 Spring Data Elasticsearch 提供的第一个响应式实现,但现在已弃用,取而代之的是使用新 Elasticsearch 客户端库提供的功能的 org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient

示例 51. 响应式 REST 客户端(已弃用)
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;

@Configuration
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {

  @Override
  @Bean
  public ReactiveElasticsearchClient reactiveElasticsearchClient() {
    final ClientConfiguration clientConfiguration = ClientConfiguration.builder() (1)
      .connectedTo("localhost:9200") //
      .build();
    return ReactiveRestClients.create(clientConfiguration);

  }
}
// ...

Mono<IndexResponse> response = client.index(request ->

 request.index("spring-data")
  .id(randomID())
  .source(singletonMap("feature", "reactive-client"));
);
 
1 使用构建器提供集群地址,设置默认值 HttpHeaders 或启用 SSL。

5.5.客户端配置

可以通过 ClientConfiguration 更改客户端行为,它允许为 SSL、连接和套接字超时、header和其他参数设置选项。

例子 52.客户端配置
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.support.HttpHeaders;

import static org.springframework.data.elasticsearch.client.elc.ElasticsearchClients.*;

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request")           (1)

ClientConfiguration clientConfiguration = ClientConfiguration.builder()
 .connectedTo("localhost:9200", "localhost:9291")           (2)
 .usingSsl()                              (3)
 .withProxy("localhost:8888")                     (4)
 .withPathPrefix("ela")                        (5)
 .withConnectTimeout(Duration.ofSeconds(5))              (6)
 .withSocketTimeout(Duration.ofSeconds(3))               (7)
 .withDefaultHeaders(defaultHeaders)                  (8)
 .withBasicAuth(username, password)                  (9)
 .withHeaders(() -> {                         (10)
  HttpHeaders headers = new HttpHeaders();
  headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
  return headers;
 })
 .withClientConfigurer(                        (11)
  ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
 	 // ...
   return clientBuilder;
 	}))
 . // ... other options
 .build();
 
1 定义默认header,如果需要自定义
2 使用构建器提供集群地址,设置默认值 HttpHeaders 或启用 SSL。
3 可选择启用 SSL。此函数存在重载,可以采用 SSLContext 或替代证书指纹,因为它是 Elasticsearch 8 在启动时输出的。
4 (可选)设置代理。
5 可选地设置一个路径前缀,主要用于不同的集群在一些反向代理之后。
6 设置连接超时。
7 设置套接字超时。
8 可选地设置标题。
9 添加基本身份验证。
10 可以指定一个 Supplier<HttpHeaders> 函数,每次在将请求发送到 Elasticsearch 之前调用该函数 - 这里,作为示例,当前时间写在header中。
11 配置创建的客户端的函数(参见 客户端配置回调 ),可以添加多次。
如上例所示添加header供应商允许注入可能随时间变化的header,例如身份验证 JWT 令牌。如果在响应式设置中使用它,供应商功能 must not 块!

5.5.1.客户端配置回调

ClientConfiguration 类提供最常用的参数来配置客户端。如果这还不够,用户可以使用 withClientConfigurer(ClientConfigurationCallback<?>) 方法添加回调函数。

提供了以下回调:

低级 Elasticsearch RestClient 的配置:

此回调提供可用于配置 Elasticsearch RestClientorg.elasticsearch.client.RestClientBuilder

ClientConfiguration.builder()
  .withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
    // configure the Elasticsearch RestClient
    return restClientBuilder;
  }))
  .build();
 
低级别 Elasticsearch RestClient 使用的 HttpAsyncClient 配置:

此回调提供了一个 org.apache.http.impl.nio.client.HttpAsyncClientBuilder 来配置 RestClient 使用的 HttpCLient。

ClientConfiguration.builder()
  .withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
    // configure the HttpAsyncClient
    return httpAsyncClientBuilder;
  }))
  .build();
 

5.5.2. Elasticsearch 7 兼容性header

使用已弃用的 RestHighLevelClient 并访问在版本 8 上运行的 Elasticsearch 集群时,需要设置兼容性header 请参阅 Elasticsearch 文档

对于命令式客户端,这必须通过设置默认header来完成,对于响应式代码,这必须使用header供应商来完成:

即使设置了这些header,也有客户端无法解析从集群返回的响应的情况。这不是 Spring Data Elasticsearch 中的错误。
HttpHeaders compatibilityHeaders = new HttpHeaders();
compatibilityHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7");
compatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json;"
  + "compatible-with=7");

ClientConfiguration clientConfiguration = ClientConfiguration.builder()
  .connectedTo("localhost:9200")
  .withProxy("localhost:8080")
  .withBasicAuth("elastic","hcraescitsale")
  .withDefaultHeaders(compatibilityHeaders)  // this variant for imperative code
  .withHeaders(() -> compatibilityHeaders)   // this variant for reactive code
  .build();
 

5.6.客户端记录

要查看实际发送到服务和从服务接收的内容,Request / Response 需要打开传输级别的日志记录,如下面的代码片段所示。这可以在 Elasticsearch 客户端中通过将 tracer 包的级别设置为“trace”来启用(参见 https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-usage-logging.html

启用传输层日志记录
<logger name="tracer" level="trace"/> 

6. Elasticsearch对象映射

Spring Data Elasticsearch 对象映射是将 Java 对象(域实体)映射到存储在 Elasticsearch 中并返回的 JSON 表示的过程。内部用于此映射的类是 MappingElasticsearchConverter

6.1.元模型对象映射

基于元模型的方法使用域类型信息从 Elasticsearch 读取/写入 Elasticsearch。这允许为特定域类型映射注册Converter实例。

6.1.1.映射注解概述

MappingElasticsearchConverter 使用元数据来驱动对象到文档的映射。元数据取自可以注解的实体属性。

以下注解可用:

  • @Document :应用于类级别以指示此类是映射到数据库的候选者。最重要的属性是(查看 API 文档以获取完整的属性列表):

  • @Id :应用于字段级别以标记用于标识目的的字段。

  • @Transient@ReadOnlyProperty@WriteOnlyProperty:有关详细信息,请参阅以下 控制向 Elasticsearch 写入和读取哪些属性 部分。

  • @PersistenceConstructor :标记一个给定的构造函数——甚至是一个包保护的构造函数——在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。

  • @Field :应用于字段级别并定义字段的属性,大部分属性映射到相应的 Elasticsearch映射 定义(以下列表不完整,请查看注解 Javadoc 以获取完整参考):

    • name :将在 Elasticsearch 文档中表示的字段名称,如果未设置,则使用 Java 字段名称。

    • type :字段类型,可以是 Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type 之一。参见 Elasticsearch Mapping Types。如果未指定字段类型,则默认为 FieldType.Auto 。这意味着,没有为该属性写入映射条目,并且 Elasticsearch 将在存储该属性的第一个数据时动态添加一个映射条目(查看 Elasticsearch 文档以了解动态映射规则)。

    • format:一种或多种内置日期格式,请参阅下一节 日期格式映射

    • pattern:一种或多种自定义日期格式,请参阅下一节 日期格式映射

    • store :标志原始字段值是否应存储在 Elasticsearch 中,默认值为 false

    • analyzersearchAnalyzernormalizer 用于指定自定义分析器和规范器。

  • @GeoPoint :将字段标记为 geo_point 数据类型。如果该字段是 GeoPoint 类的实例,则可以省略。

  • @ValueConverter 定义了一个用于转换给定属性的类。与注册的 Spring Converter 不同的是,它只转换带注解的属性,而不是给定类型的每个属性。

映射元数据基础设施在一个独立的 spring-data-commons 项目中定义,该项目与技术无关。

控制向 Elasticsearch 写入和读取哪些属性

本节详细介绍了定义属性值是写入 Elasticsearch 还是从 Elasticsearch 读取的注解。

@Transient:使用此注解注解的属性不会写入映射,它的值不会发送到 Elasticsearch,并且当从 Elasticsearch 返回文档时,不会在生成的实体中设置此属性。

@ReadOnlyProperty : 具有此注解的属性不会将其值写入 Elasticsearch,但在返回数据时,该属性将填充 Elasticsearch 文档中返回的值。一个用例是在索引映射中定义的运行时字段。

@WriteOnlyProperty :具有此注解的属性将其值存储在 Elasticsearch 中,但在读取文档时不会设置任何值。这可以用于例如应该进入 Elasticsearch 索引但不在其他地方使用的合成字段。

日期格式映射

TemporalAccessor 派生或类型为 java.util.Date 的属性必须具有类型为 FieldType.Date@Field 注解,或者必须为此类型注册自定义转换器。本段描述了 FieldType.Date 的使用。

@Field 注解的两个属性定义了将哪种日期格式信息写入映射(另请参见 Elasticsearch 内置格式Elasticsearch Custom Date Formats

format 属性用于定义至少一种预定义格式。如果未定义,则使用默认值 _date_optional_timeepoch_millis

pattern 属性可用于添加其他自定义格式字符串。如果只想使用自定义日期格式,则必须将 format 属性设置为空 {}

下表显示了不同的属性和根据它们的值创建的映射:

注解 Elasticsearch 映射中的格式化字符串

@Field(type=FieldType.Date)

“date_optional_time||epoch_millis”,

@Field(type=FieldType.Date, format=DateFormat.basic_date)

"basic_date"

@Field(type=FieldType.Date, format={DateFormat.basic_date, DateFormat.basic_time})

"basic_date||basic_time"

@Field(type=FieldType.Date, pattern="dd.MM.uuuu")

“date_optional_time||epoch_millis||dd.MM.uuuu”,

@Field(type=FieldType.Date, format={}, pattern="dd.MM.uuuu")

“dd.MM.uuuu”

如果您使用自定义日期格式,则需要使用 uuuu 而不是 yyyy 作为年份。这是由于 Elasticsearch 7 中的变化

检查 org.springframework.data.elasticsearch.annotations.DateFormat 枚举的代码以获得预定义值及其模式的完整列表。

范围类型

当一个字段被注解为 Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,Ip_Range 之一的类型时,该字段必须是将映射到 Elasticsearch 范围的类的实例,例如:

class SomePersonData {

  @Field(type = FieldType.Integer_Range)
  private ValidAge validAge;

  // getter and setter
}

class ValidAge {
  @Field(name="gte")
  private Integer from;

  @Field(name="lte")
  private Integer to;

  // getter and setter
}
 

作为替代方案,Spring Data Elasticsearch 提供了一个 Range<T> 类,因此前面的示例可以写成:

class SomePersonData {

  @Field(type = FieldType.Integer_Range)
  private Range<Integer> validAge;

  // getter and setter
}
 

<T> 类型支持的类是 IntegerLongFloatDoubleDate 和实现 TemporalAccessor 接口的类。

映射字段名称

在没有进一步配置的情况下,Spring Data Elasticsearch 将使用对象的属性名称作为 Elasticsearch 中的字段名称。这可以通过在该属性上使用 @Field 注解来更改单个字段。

也可以在客户端 (Elasticsearch 客户端) 的配置中定义一个 FieldNamingStrategy。例如,如果配置了 SnakeCaseFieldNamingStrategy,则对象的属性 sampleProperty 将映射到 Elasticsearch 中的 sample_property。 A FieldNamingStrategy 适用于所有实体;可以通过在属性上使用 @Field 设置特定名称来覆盖它。

非字段支持的属性

通常,实体中使用的属性是实体类的字段。在某些情况下,属性值是在实体中计算出来的,应该存储在 Elasticsearch 中。在这种情况下,getter 方法 (getProperty()) 可以使用 @Field 注释进行注释,此外该方法必须使用 @AccessType(AccessType.Type .PROPERTY) 进行注释。在这种情况下需要的第三个注释是 @WriteOnlyProperty ,因为这样的值只写入 Elasticsearch。一个完整的例子:

@Field(type = Keyword)
@WriteOnlyProperty
@AccessType(AccessType.Type.PROPERTY)
public String getProperty() {
	return "some value that is calculated here";
}
 
其他属性注解
@IndexedIndexName

可以在实体的 String 属性上设置此注解。此属性不会写入映射,不会存储在 Elasticsearch 中,也不会从 Elasticsearch 文档中读取它的值。在实体持久化后,例如调用 ElasticsearchOperations.save(T entity) ,从该调用返回的实体将包含实体在该属性中保存到的索引的名称。当索引名称由 bean 动态设置时,或者写入别名时,这很有用。

将一些值放入此类属性不会设置存储实体的索引!

6.1.2.映射规则

类型提示

映射使用 type hints 嵌入到发送到服务的文档中以允许泛型类型映射。这些类型提示在文档中表示为 _class 属性,并为每个聚合根编写。

示例 53. 类型提示
public class Person {       (1)
 @Id String id;
 String firstname;
 String lastname;
}
 
{
 "_class" : "com.example.Person", (1)
 "id" : "cb7bef",
 "firstname" : "Sarah",
 "lastname" : "Connor"
} 
1 默认情况下,域类型类名用于类型提示。

可以配置类型提示来保存自定义信息。使用 @TypeAlias 注解来执行此操作。

确保将带有 @TypeAlias 的类型添加到初始实体集 (AbstractElasticsearchConfiguration#getInitialEntitySet),以便在首次从存储中读取数据时实体信息可用。
示例 54. 带有别名的类型提示
@TypeAlias("human")        (1)
public class Person {

 @Id String id;
 // ...
}
 
{
 "_class" : "human",       (1)
 "id" : ...
} 
1 编写实体时使用配置的别名。
除非属性类型为 Object 、接口或实际值类型与属性声明不匹配,否则不会为嵌套对象编写类型提示。
禁用类型提示

当应该使用的索引已经存在而没有在其映射中定义类型提示并且映射模式设置为严格时,可能需要禁用类型提示的写入。在这种情况下,写入类型提示会产生错误,因为无法自动添加该字段。

可以通过覆盖从 AbstractElasticsearchConfiguration 派生的配置类中的方法 writeTypeHints() 来禁用整个应用程序的类型提示(请参阅 Elasticsearch 客户端 )。

作为替代方案,可以使用 @Document 注解为单个索引禁用它们:

@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)
 
我们强烈建议不要禁用类型提示。仅在您被迫这样做时才这样做。在多态数据或文档检索可能完全失败的情况下,禁用类型提示可能导致无法从 Elasticsearch 正确检索文档。
地理空间类型

PointGeoPoint 等地理空间类型被转换为 lat/lon 对。

示例 55. 地理空间类型
public class Address {
 String city, street;
 Point location;
}
 
{
 "city" : "Los Angeles",
 "street" : "2800 East Observatory Road",
 "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
} 
GeoJson 类型

Spring Data Elasticsearch 通过提供接口GeoJson 和不同几何图形的实现来支持 GeoJson 类型。它们根据 GeoJson 规范映射到 Elasticsearch 文档。在编写索引映射时,实体的相应属性在索引映射中指定为geo_shape。 (同时检查 Elasticsearch 文档

例子 56. GeoJson 类型
public class Address {

 String city, street;
 GeoJsonPoint location;
}
 
{
 "city": "Los Angeles",
 "street": "2800 East Observatory Road",
 "location": {
  "type": "Point",
  "coordinates": [-118.3026284, 34.118347]
 }
} 

实现了以下 GeoJson 类型:

  • GeoJsonPoint

  • GeoJsonMultiPoint

  • GeoJsonLineString

  • GeoJsonMultiLineString

  • GeoJsonPolygon

  • GeoJsonMultiPolygon

  • GeoJsonGeometryCollection

集合

对于 Collections 内的值,在涉及 type hintsCustom Conversions 时应用与聚合根相同的映射规则。

示例 57. 集合
public class Person {

 // ...

 List<Person> friends;

}
 
{
 // ...

 "friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
} 
map

对于 Maps 内的值,在涉及 type hintsCustom Conversions 时应用与聚合根相同的映射规则。然而,映射键需要一个字符串才能被 Elasticsearch 处理。

示例 58. 集合
public class Person {

 // ...

 Map<String, Address> knownLocations;

}
 
{
 // ...

 "knownLocations" : {
  "arrivedAt" : {
    "city" : "Los Angeles",
    "street" : "2800 East Observatory Road",
    "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
   }
 }
} 

6.1.3.自定义转换

查看 上一节 ElasticsearchCustomConversions 中的 Configuration 允许为映射域和简单类型注册特定规则。

示例 59.元模型对象映射配置
@Configuration
public class Config extends ElasticsearchConfiguration {

	@NonNull
	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
				.connectedTo("localhost:9200") //
				.build();
	}

 @Bean
 @Override
 public ElasticsearchCustomConversions elasticsearchCustomConversions() {
  return new ElasticsearchCustomConversions(
   Arrays.asList(new AddressToMap(), new MapToAddress()));    (1)
 }

 @WritingConverter                         (2)
 static class AddressToMap implements Converter<Address, Map<String, Object>> {

  @Override
  public Map<String, Object> convert(Address source) {

   LinkedHashMap<String, Object> target = new LinkedHashMap<>();
   target.put("ciudad", source.getCity());
   // ...

   return target;
  }
 }

 @ReadingConverter                         (3)
 static class MapToAddress implements Converter<Map<String, Object>, Address> {

  @Override
  public Address convert(Map<String, Object> source) {

   // ...
   return address;
  }
 }
}
 
{
 "ciudad" : "Los Angeles",
 "calle" : "2800 East Observatory Road",
 "localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
} 
1 添加 Converter 实现。
2 设置用于将 DomainType 写入 Elasticsearch 的 Converter
3 设置用于从搜索结果中读取 DomainTypeConverter

7. Elasticsearch操作

Spring Data Elasticsearch 使用多个接口来定义可以针对 Elasticsearch 索引调用的操作(有关响应式接口的描述,请参阅 响应式 Elasticsearch 操作)。

  • IndexOperations 定义索引级别的操作,如创建或删除索引。

  • DocumentOperations 定义了根据实体的 id 存储、更新和检索实体的操作。

  • SearchOperations 定义使用查询搜索多个实体的操作

  • ElasticsearchOperations 结合了 DocumentOperationsSearchOperations 接口。

这些接口对应于 ElasticsearchAPI 的结构。

接口的默认实现提供:

  • 索引管理功能。

  • 域类型的读/写映射支持。

  • 丰富的查询和条件 api。

  • 资源管理和异常转换。

索引管理和自动创建索引和映射。

IndexOperations 接口和提供的实现可以从 ElasticsearchOperations 实例获得 - 例如调用 operations.indexOps(clazz) - 使用户能够在 Elasticsearch 集群中创建索引、放置映射或存储模板和别名信息。可以使用 @Setting 注解设置将要创建的索引的详细信息,有关详细信息,请参阅 索引设置

None of these operations are done automatically 通过 IndexOperationsElasticsearchOperations 的实现。调用这些方法是用户的责任。

使用 Spring Data Elasticsearch 存储库时支持自动创建索引和编写映射,请参阅 使用相应的映射自动创建索引

7.1.使用示例

该示例展示了如何在 Spring REST 控制器中使用注入的 ElasticsearchOperations 实例。该示例假定 Person 是一个用 @Document@Id 等注解的类(参见 映射注解概述 )。

示例 60. ElasticsearchOperations 用法
@RestController
@RequestMapping("/")
public class TestController {

 private ElasticsearchOperations elasticsearchOperations;

 public TestController(ElasticsearchOperations elasticsearchOperations) { (1)
  this.elasticsearchOperations = elasticsearchOperations;
 }

 @PostMapping("/person")
 public String save(@RequestBody Person person) {             (2)
  Person savedEntity = elasticsearchOperations.save(person);
  return savedEntity.getId();
 }

 @GetMapping("/person/{id}")
 public Person findById(@PathVariable("id") Long id) {          (3)
  Person person = elasticsearchOperations.get(id.toString(), Person.class);
  return person;
 }
}
 
1 让 Spring 在构造函数中注入提供的 ElasticsearchOperations bean。
2 在 Elasticsearch 集群中存储一些实体。 id 从返回的实体中读取,因为它在 person 对象中可能为 null 并且是由 Elasticsearch 创建的。
3 使用 get by id 检索实体。

要查看 ElasticsearchOperations 的全部可能性,请参阅 API 文档。

7.2.响应式性 Elasticsearch 操作

ReactiveElasticsearchOperations 是使用 ReactiveElasticsearchClient 对 Elasticsearch 集群执行高级命令的网关。

ReactiveElasticsearchTemplateReactiveElasticsearchOperations 的默认实现。

7.2.1.响应式性 Elasticsearch 操作

要开始,ReactiveElasticsearchOperations 需要了解要使用的实际客户端。请参阅 响应式Rest 客户端 了解有关客户端的详细信息以及如何配置它。

响应式性操作用法

ReactiveElasticsearchOperations 让您可以保存、查找和删除领域对象,并将这些对象映射到存储在 Elasticsearch 中的文档。

考虑以下:

例子 61. 使用 ReactiveElasticsearchOperations
@Document(indexName = "marvel")
public class Person {

 private @Id String id;
 private String name;
 private int age;
 // Getter/Setter omitted...
}
 
ReactiveElasticsearchOperations operations;

// ...

operations.save(new Person("Bruce Banner", 42))          (1)
 .doOnNext(System.out::println)
 .flatMap(person -> operations.get(person.id, Person.class))   (2)
 .doOnNext(System.out::println)
 .flatMap(person -> operations.delete(person))          (3)
 .doOnNext(System.out::println)
 .flatMap(id -> operations.count(Person.class))          (4)
 .doOnNext(System.out::println)
 .subscribe();                          (5)
 

以上在控制台上输出以下序列。

> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
> QjWCWWcBXiLAnp77ksfR
> 0 
1 将新的 Person 文档插入 marvel 索引。 id 在服务端生成并设置到返回的实例中。
2 marvel 索引中查找具有匹配 idPerson
3 marvel 索引中删除从给定实例中提取的具有匹配 idPerson
4 统计 marvel 索引中的文档总数。
5 别忘了 subscribe()

7.3.搜索结果类型

当使用 DocumentOperations 接口的方法检索文档时,将只返回找到的实体。当使用 SearchOperations 接口的方法进行搜索时,每个实体的附加信息都可用,例如找到的实体的 scoresortValues

为了返回此信息,每个实体都包装在一个 SearchHit 对象中,该对象包含此实体特定的附加信息。这些 SearchHit 对象本身在 SearchHits 对象中返回,该对象还包含有关整个搜索的信息,如 maxScore 或请求的聚合。现在可以使用以下类和接口:

SearchHit<T>

包含以下信息:

  • Id

  • 得分

  • 排序值

  • 高亮字段

  • 内部命中(这是一个嵌入的 SearchHits 对象,包含最终返回的内部命中)

  • 检索到的 <T> 类型的实体

SearchHits<T>

包含以下信息:

  • 总命中数

  • 总命中关系

  • 最高分

  • SearchHit<T> 对象列表

  • 返回的聚合

  • 返回建议结果

SearchPage<T>

定义一个 Spring Data Page,其中包含一个 SearchHits<T> 元素,可用于使用存储库方法进行分页访问。

SearchScrollHits<T>

ElasticsearchRestTemplate 中的低级滚动 API 函数返回,它使用 Elasticsearch 滚动 ID 丰富了 SearchHits<T>

SearchHitsIterator<T>

SearchOperations 接口的流函数返回的迭代器。

ReactiveSearchHits

ReactiveSearchOperations 有返回 Mono<ReactiveSearchHits<T>> 的方法,它包含与 SearchHits<T> 对象相同的信息,但会将包含的 SearchHit<T> 对象作为 Flux<SearchHit<T>> 而不是列表提供。

7.4.查询

SearchOperationsReactiveSearchOperations 接口中定义的几乎所有方法都有一个 Query 参数,该参数定义要执行的搜索查询。 Query 是一个接口,Spring Data Elasticsearch 提供了三种实现:CriteriaQueryStringQueryNativeQuery

7.4.1.条件查询

基于 CriteriaQuery 的查询允许创建查询来搜索数据,而无需了解 Elasticsearch 查询的语法或基础知识。它们允许用户通过简单地链接和组合 Criteria 对象来构建查询,这些对象指定了搜索文档必须满足的条件。

当谈论 AND 或 OR 组合条件时请记住,在 Elasticsearch 中,AND 被转换为 must 条件,OR 被转换为 should

Criteria 及其用法最好通过示例来解释(假设我们有一个带有 price 属性的 Book 实体):

例子 62. 获取给定价格的书
Criteria criteria = new Criteria("price").is(42.0);
Query query = new CriteriaQuery(criteria);
 

同一字段的条件可以链接,它们将与逻辑 AND 组合:

例子 63. 获取给定价格的书
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0);
Query query = new CriteriaQuery(criteria);
 

链接 Criteria 时,默认使用 AND 逻辑:

示例 64. 获取所有名字为 James 和姓氏为 Miller 的人:
Criteria criteria = new Criteria("lastname").is("Miller") (1)
 .and("firstname").is("James")              (2)
Query query = new CriteriaQuery(criteria);
 
1 第一个Criteria
2 and() 创建一个新的 Criteria 并将其链接到第一个。

如果要创建嵌套查询,则需要为此使用子查询。假设我们要查找所有姓氏为 Miller 且名字为 JackJohn 的人:

例子 65. 嵌套子查询
Criteria miller = new Criteria("lastName").is("Miller") (1)
 .subCriteria(                     (2)
  new Criteria().or("firstName").is("John")      (3)
   .or("firstName").is("Jack")            (4)
 );
Query query = new CriteriaQuery(criteria);
 
1 为姓氏创建第一个 Criteria
2 这与 AND 结合到一个子标准
3 此子标准是名字 John 的 OR 组合
4 和名字杰克

请参阅 Criteria 类的 API 文档以获得不同可用操作的完整概述。

7.4.2.字符串查询

此类将 Elasticsearch 查询作为 JSON 字符串。以下代码显示了搜索名字为“Jack”的人的查询:

Query query = new StringQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
SearchHits<Person> searchHits = operations.search(query, Person.class);
 

如果您已经有要使用的 Elasticsearch 查询,则使用 StringQuery 可能是合适的。

7.4.3.原生查询

NativeQuery 是在您有复杂查询或无法使用 Criteria API 表达的查询时使用的类,例如在构建查询和使用聚合时。它允许使用 Elasticsearch 库中所有不同的 co.elastic.clients.elasticsearch._types.query_dsl.Query 实现,因此命名为“native”。

以下代码显示了如何搜索具有给定名字的人,并且对于找到的文档有一个术语聚合来计算这些人的姓氏出现的次数:

Query query = NativeQuery.builder()
	.withAggregation("lastNames", Aggregation.of(a -> a
		.terms(ta -> ta.field("last-name").size(10))))
	.withQuery(q -> q
		.match(m -> m
			.field("firstName")
			.query(firstName)
		)
	)
	.withPageable(pageable)
	.build();

SearchHits<Person> searchHits = operations.search(query, Person.class);
 

[[elasticsearch.operations.searchtemplateScOp§query]] === SearchTemplateQuery

这是 Query 接口的特殊实现,可与存储的搜索模板结合使用。有关详细信息,请参阅 搜索模板支持

8. Elasticsearch Repository

本章包括 Elasticsearch 存储库实现的详细信息。

示例 66. 示例 Book 实体
@Document(indexName="books")
class Book {
  @Id
  private String id;

  @Field(type = FieldType.text)
  private String name;

  @Field(type = FieldType.text)
  private String summary;

  @Field(type = FieldType.Integer)
  private Integer price;

	// getter/setter ...
}
 

8.1.使用相应的映射自动创建索引

@Document 注解有一个参数 createIndex 。如果此参数设置为 true - 这是默认值 - Spring Data Elasticsearch 将在引导存储库支持期间在应用程序启动时检查 @Document 注解定义的索引是否存在。

如果它不存在,则将创建索引,并将从实体注解派生的映射(参见 Elasticsearch 对象映射 )写入新创建的索引。可以使用 @Setting 注解设置将要创建的索引的详细信息,有关详细信息,请参阅 索引设置

8.2.查询方式

8.2.1.查询查找策略

Elasticsearch 模块支持所有基本查询构建功能,如字符串查询、原生搜索查询、基于条件的查询或从方法名称派生的查询。

声明查询

从方法名称派生查询并不总是足够的和/或可能导致不可读的方法名称。在这种情况下,可以使用 @Query 注解(参见 使用@Query 注解 )。

8.2.2.查询创建

通常,Elasticsearch 的查询创建机制按照 查询方法 中的描述工作。以下是 Elasticsearch 查询方法转换成的简短示例:

示例 67. 从方法名称创建查询
interface BookRepository extends Repository<Book, String> {
 List<Book> findByNameAndPrice(String name, Integer price);
}
 

上面的方法名会被翻译成下面的 Elasticsearch json 查询

{
  "query": {
    "bool" : {
      "must" : [
        { "query_string" : { "query" : "?", "fields" : [ "name" ] } },
        { "query_string" : { "query" : "?", "fields" : [ "price" ] } }
      ]
    }
  }
} 

Elasticsearch 支持的关键字列表如下所示。

表 3. 方法名称中支持的关键字
关键词 示例 Elasticsearch 查询字符串

And

findByNameAndPrice

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}

Or

findByNameOrPrice

{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}

Is

findByName

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}

Not

findByNameNot

{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}

Between

findByPriceBetween

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

LessThan

findByPriceLessThan

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }}

LessThanEqual

findByPriceLessThanEqual

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

GreaterThan

findByPriceGreaterThan

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }}

GreaterThanEqual

findByPriceGreaterThan

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}

Before

findByPriceBefore

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

After

findByPriceAfter

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}

Like

findByNameLike

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

StartingWith

findByNameStartingWith

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

EndingWith

findByNameEndingWith

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

Contains/Containing

findByNameContaining

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

In(注解为 FieldType.Keyword 时)

findByNameIn(Collection<String>names)

{ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}

In

findByNameIn(Collection<String>names)

{ "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}}

NotIn(注解为 FieldType.Keyword 时)

findByNameNotIn(Collection<String>names)

{ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}

NotIn

findByNameNotIn(Collection<String>names)

{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}

True

findByAvailableTrue

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }}

False

findByAvailableFalse

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }}

OrderBy

findByAvailableTrueOrderByNameDesc

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] }

Exists

findByNameExists

{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}

IsNull

findByNameIsNull

{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}

IsNotNull

findByNameIsNotNull

{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}

IsEmpty

findByNameIsEmpty

{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}

IsNotEmpty

findByNameIsNotEmpty

{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}

不支持使用 GeoJson 参数构建 Geo-shape 查询的方法名称。如果您需要在存储库中具有这样的功能,请在自定义存储库实现中使用 ElasticsearchOperationsCriteriaQuery

8.2.3.方法返回类型

可以将存储库方法定义为具有以下用于返回多个元素的返回类型:

  • List<T>

  • Stream<T>

  • SearchHits<T>

  • List<SearchHit<T>>

  • Stream<SearchHit<T>>

  • SearchPage<T>

8.2.4.使用@Query 注解

示例 68. 使用 @Query 注解声明对方法的查询。

传递给该方法的参数可以插入到查询字符串中的占位符中。占位符的形式为 ?0?1?2 等,用于第一个、第二个、第三个参数等。

interface BookRepository extends ElasticsearchRepository<Book, String> {
  @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
  Page<Book> findByName(String name,Pageable pageable);
}
 

设置为注解参数的字符串必须是有效的 Elasticsearch JSON 查询。它将作为查询元素的值发送到 Easticsearch;例如,如果使用参数 John 调用该函数,它将产生以下查询主体:

{
 "query": {
  "match": {
   "name": {
    "query": "John"
   }
  }
 }
} 
示例 69. @Query 方法上的注解采用 Collection 参数

存储库方法,例如

@Query("{\"ids\": {\"values\": ?0 }}")
List<SampleEntity> getByIds(Collection<String> ids);
 

将使IDs 查询返回所有匹配的文档。因此,使用 List of ["id1", "id2", "id3"] 调用该方法将生成查询主体

{
 "query": {
  "ids": {
   "values": ["id1", "id2", "id3"]
  }
 }
} 

8.3.响应式 Elasticsearch Repository

响应式 Elasticsearch 存储库支持建立在 使用 Spring Data Repository 中解释的核心存储库支持之上,利用由 响应式客户端(已弃用) 执行的 Reactive Elasticsearch Operations 提供的操作。

Spring Data Elasticsearch 响应式存储库支持使用 Project Reactor 作为其选择的响应式组合库。

有3个主要接口要使用:

  • ReactiveRepository

  • ReactiveCrudRepository

  • ReactiveSortingRepository

8.3.1.用法

要使用 Repository 访问存储在 Elasticsearch 中的域对象,只需为其创建一个接口。在您真正继续进行之前,您需要一个实体。

示例 70. 示例 Person 实体
public class Person {

 @Id
 private String id;
 private String firstname;
 private String lastname;
 private Address address;

 // … getters and setters omitted
}
 
请注意 id 属性需要是 String 类型。
示例 71. 持久化 Person 实体的基本存储库接口
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

 Flux<Person> findByFirstname(String firstname);                  (1)

 Flux<Person> findByFirstname(Publisher<String> firstname);            (2)

 Flux<Person> findByFirstnameOrderByLastname(String firstname);          (3)

 Flux<Person> findByFirstname(String firstname, Sort sort);            (4)

 Flux<Person> findByFirstname(String firstname, Pageable page);          (5)

 Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);    (6)

 Mono<Person> findFirstByLastname(String lastname);                (7)

 @Query("{ \"bool\" : { \"must\" : { \"term\" : { \"lastname\" : \"?0\" } } } }")
 Flux<Person> findByLastname(String lastname);                   (8)

 Mono<Long> countByFirstname(String firstname)                   (9)

 Mono<Boolean> existsByFirstname(String firstname)                 (10)

 Mono<Long> deleteByFirstname(String firstname)                  (11)
} 
1 该方法显示对具有给定 lastname 的所有人的查询。
2 Finder 方法等待来自 Publisher 的输入以绑定 firstname 的参数值。
3 Finder 方法按 lastname 排序匹配文档。
4 Finder 方法通过 Sort 参数定义的表达式对匹配文档进行排序。
5 使用 Pageable 将偏移量和排序参数传递给数据库。
6 Finder 方法使用 And / Or 关键字连接标准。
7 找到第一个匹配的实体。
8 该方法显示了对所有具有给定 lastname 的人的查询,该查询通过使用给定参数运行带注解的 @Query 来查找。
9 计算所有匹配 firstname 的实体。
10 检查是否至少存在一个具有匹配 firstname 的实体。
11 删除所有匹配 firstname 的实体。

8.3.2.配置

对于 Java 配置,使用 @EnableReactiveElasticsearchRepositories 注解。如果没有配置基础包,基础设施会扫描注解配置类的包。

以下清单显示了如何为存储库使用 Java 配置:

示例 72.存储库的 Java 配置
@Configuration
@EnableReactiveElasticsearchRepositories
public class Config extends AbstractReactiveElasticsearchConfiguration {

 @Override
 public ReactiveElasticsearchClient reactiveElasticsearchClient() {
  return ReactiveRestClients.create(ClientConfiguration.localhost());
 }
}
 

因为前面示例中的存储库扩展了 ReactiveSortingRepository ,所以所有 CRUD 操作以及对实体进行排序访问的方法都可用。使用存储库实例是将其注入客户端的依赖性问题,如以下示例所示:

示例 73. 对 Person 实体的排序访问
public class PersonRepositoryTests {

 @Autowired ReactivePersonRepository repository;

 @Test
 public void sortsElementsCorrectly() {

  Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));

  // ...
 }
}
 

8.4.存储库方法的注解

8.4.1. @强调

存储库方法上的 @Highlight 注解定义了应包括返回实体突出显示的哪些字段。要在 Book 的名称或摘要中搜索某些文本并突出显示找到的数据,可以使用以下存储库方法:

interface BookRepository extends Repository<Book, String> {

  @Highlight(fields = {
    @HighlightField(name = "name"),
    @HighlightField(name = "summary")
  })
  SearchHits<Book> findByNameOrSummary(String text, String summary);
}
 

可以像上面那样定义多个要高亮显示的字段,@Highlight@HighlightField 注解都可以进一步自定义为 @HighlightParameters 注解。检查 Javadocs 以了解可能的配置选项。

在搜索结果中,可以从 SearchHit 类中检索突出显示数据。

8.4.2. @SourceFilters

有时,用户不需要拥有从搜索返回的实体的所有属性,而只需要一个子集。 Elasticsearch 提供源过滤以减少通过Web传输到应用程序的数据量。

当使用 Query 实现和 ElasticsearchOperations 时,这很容易通过在查询上设置源过滤器来实现。

使用存储库方法时,有 @SourceFilters 注解:

interface BookRepository extends Repository<Book, String> {

  @SourceFilters(includes = "name")
  SearchHits<Book> findByName(String text);
}
 

在此示例中,返回的 Book 对象的所有属性都将是 null 除了名称。

8.5.基于注解的配置

可以通过 JavaConfig 使用注解激活 Spring Data Elasticsearch 存储库支持。

示例 74.使用 JavaConfig 的 Spring Data Elasticsearch 存储库
@Configuration
@EnableElasticsearchRepositories(               (1)
 basePackages = "org.springframework.data.elasticsearch.repositories"
 )
static class Config {

 @Bean
 public ElasticsearchOperations elasticsearchTemplate() {  (2)
   // ...
 }
}

class ProductService {

 private ProductRepository repository;            (3)

 public ProductService(ProductRepository repository) {
  this.repository = repository;
 }

 public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
  return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
 }
}
 
1 EnableElasticsearchRepositories 注解激活存储库支持。如果没有配置基础包,它将使用它所在的配置类之一。
2 通过使用 Elasticsearch 操作 章节中显示的配置之一提供类型为 ElasticsearchOperations 的名为 elasticsearchTemplate 的 Bean。
3 让 Spring 将 Repository bean 注入到您的类中。

8.6.使用 CDI 的 Elasticsearch Repository

还可以使用 CDI 功能设置 Spring Data Elasticsearch 存储库。

示例 75.使用 CDI 的 Spring Data Elasticsearch 存储库
class ElasticsearchTemplateProducer {

 @Produces
 @ApplicationScoped
 public ElasticsearchOperations createElasticsearchTemplate() {
  // ...                (1)
 }
}

class ProductService {

 private ProductRepository repository; (2)
 public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
  return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
 }
 @Inject
 public void setRepository(ProductRepository repository) {
  this.repository = repository;
 }
}
 
1 使用 Elasticsearch 操作 章节中使用的相同调用创建组件。
2 让 CDI 框架将存储库注入您的类。

8.7.Spring命名空间

Spring Data Elasticsearch 模块包含一个自定义命名空间,允许定义存储库 bean 以及用于实例化 ElasticsearchServer 的元素。

使用 repositories 元素查找 Spring Data 存储库,如 创建Repository实例 中所述。

示例 76.使用命名空间设置 Elasticsearch 存储库
<?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:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/data/elasticsearch
    https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">

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

</beans> 

使用 Transport ClientRest Client 元素在上下文中注册 Elasticsearch Server 的实例。

示例 77. 使用命名空间传输客户端
<?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:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/data/elasticsearch
    https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">

 <elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" />

</beans> 
示例 78.使用命名空间的 Rest 客户端
<?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:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
    xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch
    https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

 <elasticsearch:rest-client id="restClient" hosts="http://localhost:9200">

</beans> 

9.审计

9.1.基本

Spring Data 提供了复杂的支持来透明地跟踪谁创建或更改了实体以及更改发生的时间。要从该功能中受益,您必须为您的实体类配备审计元数据,这些元数据可以使用注解或通过实现接口来定义.此外,必须通过注解配置或 XML 配置启用审计以注册所需的基础结构组件。有关配置示例,请参阅存储特定部分。

不需要仅跟踪创建和修改日期的应用程序会使其实体实现 AuditorAware

9.1.1.基于注解的审计元数据

我们提供 @CreatedBy@LastModifiedBy 来捕获创建或修改实体的用户,以及 @CreatedDate@LastModifiedDate 来捕获更改发生的时间。

示例 79. 一个被审计的实体
class Customer {

 @CreatedBy
 private User user;

 @CreatedDate
 private Instant createdDate;

 // … further properties omitted
}
 

如您所见,可以根据要捕获的信息有选择地应用注解。指示在进行更改时捕获的注解可用于 JDK8 日期和时间类型 longLong 以及旧版 Java DateCalendar 类型的属性。

审计元数据不一定需要存在于根级实体中,但可以添加到嵌入式实体中(取决于实际使用的存储),如下面的代码片段所示。

示例 80. 嵌入式实体中的审计元数据
class Customer {

 private AuditMetadata auditingMetadata;

 // … further properties omitted
}

class AuditMetadata {

 @CreatedBy
 private User user;

 @CreatedDate
 private Instant createdDate;

}
 

9.1.2.基于接口的审计元数据

如果您不想使用注解来定义审计元数据,您可以让您的域类实现 Auditable 接口。它公开了所有审计属性的 setter 方法。

9.1.3. AuditorAware

如果您使用 @CreatedBy@LastModifiedBy ,审计基础设施需要以某种方式了解当前主体。为此,我们提供了一个 AuditorAware<T> SPI 接口,您必须实现该接口以告知基础schema当前与应用程序交互的用户或系统是谁。通用类型 T 定义了用 @CreatedBy@LastModifiedBy 注解的属性必须是什么类型。

以下示例显示了使用 Spring Security 的 Authentication 对象的接口的实现:

例子81.基于Spring Security的AuditorAware的实现
class SpringSecurityAuditorAware implements AuditorAware<User> {

 @Override
 public Optional<User> getCurrentAuditor() {

  return Optional.ofNullable(SecurityContextHolder.getContext())
      .map(SecurityContext::getAuthentication)
      .filter(Authentication::isAuthenticated)
      .map(Authentication::getPrincipal)
      .map(User.class::cast);
 }
}
 

该实现访问 Spring Security 提供的 Authentication 对象,并查找您在 UserDetailsService 实现中创建的自定义 UserDetails 实例。我们在这里假设您通过 UserDetails 实现公开域用户,但是,根据找到的 Authentication,您也可以从任何地方查找它。

9.1.4. ReactiveAuditorAware

使用响应式基础设施时,您可能希望利用上下文信息来提供 @CreatedBy@LastModifiedBy 信息。我们提供了一个 ReactiveAuditorAware<T> SPI 接口,您必须实现该接口以告知基础schema当前与应用程序交互的用户或系统是谁。通用类型 T 定义了用 @CreatedBy@LastModifiedBy 注解的属性必须是什么类型。

以下示例显示了使用响应式 Spring Security 的 Authentication 对象的接口的实现:

示例 82. 基于 Spring Security 的ReactiveAuditorAware实现
class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {

 @Override
 public Mono<User> getCurrentAuditor() {

  return ReactiveSecurityContextHolder.getContext()
        .map(SecurityContext::getAuthentication)
        .filter(Authentication::isAuthenticated)
        .map(Authentication::getPrincipal)
        .map(User.class::cast);
 }
}
 

该实现访问 Spring Security 提供的 Authentication 对象,并查找您在 UserDetailsService 实现中创建的自定义 UserDetails 实例。我们在这里假设您通过 UserDetails 实现公开域用户,但是,根据找到的 Authentication,您也可以从任何地方查找它。

9.2.Elasticsearch 审计

9.2.1.准备实体

为了让审计代码能够决定实体实例是否是新的,实体必须实现定义如下的Persistable<ID>接口:

package org.springframework.data.domain;

import org.springframework.lang.Nullable;

public interface Persistable<ID> {
  @Nullable
  ID getId();

  boolean isNew();
}
 

由于 Id 的存在不足以确定实体在 Elasticsearch 中是否是新的,因此需要额外的信息。一种方法是使用与创建相关的审计字段来做出此决定:

Person 实体可能如下所示 - 为简洁起见省略了 getter 和 setter 方法:

@Document(indexName = "person")
public class Person implements Persistable<Long> {
  @Id private Long id;
  private String lastName;
  private String firstName;
  @CreatedDate
  @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
  private Instant createdDate;
  @CreatedBy
  private String createdBy
  @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
  @LastModifiedDate
  private Instant lastModifiedDate;
  @LastModifiedBy
  private String lastModifiedBy;

  public Long getId() {                         (1)
    return id;
  }

  @Override
  public boolean isNew() {
    return id == null || (createdDate == null && createdBy == null); (2)
  }
}
 
1 吸气剂是接口所需的实现
2 如果一个对象没有 id 或没有设置包含创建属性的字段,则该对象是新的。

9.2.2.激活审计

在设置实体并提供 AuditorAware - 或 ReactiveAuditorAware - 之后,必须通过在配置类上设置 @EnableElasticsearchAuditing 来激活审计:

@Configuration
@EnableElasticsearchRepositories
@EnableElasticsearchAuditing
class MyConfiguration {
  // configuration code
}
 

使用响应式堆时,这必须是:

@Configuration
@EnableReactiveElasticsearchRepositories
@EnableReactiveElasticsearchAuditing
class MyConfiguration {
  // configuration code
}
 

如果您的代码包含多个用于不同类型的 AuditorAware bean,则必须提供 bean 的名称以用作 @EnableElasticsearchAuditing 注解的 auditorAwareRef 参数的参数。

10.实体回调

Spring Data 基础结构提供了用于在调用某些方法之前和之后修改实体的挂钩。那些所谓的 EntityCallback 实例提供了一种方便的方法来检查并可能以回调方式修改实体。
EntityCallback 看起来很像专门的 ApplicationListener。一些 Spring Data 模块发布允许修改给定实体的存储特定事件(例如 BeforeSaveEvent )。在某些情况下,例如在使用不可变类型时,这些事件可能会导致麻烦。此外,事件发布依赖于 ApplicationEventMulticaster 。如果使用异步 TaskExecutor 配置它可能会导致不可投影的结果,因为事件处理可以分叉到线程上。

实体回调提供同步和响应式 API 的集成点,以保证在处理链中定义明确的检查点按顺序执行,返回可能修改的实体或响应式包装器类型。

实体回调通常按 API 类型分隔。这种分离意味着同步 API 只考虑同步实体回调,而响应式实现只考虑响应式实体回调。

Spring Data Commons 2.2 引入了实体回调 API。这是应用实体修改的推荐方式。现有存储特定 ApplicationEvents 仍发布 before 调用可能已注册的 EntityCallback 实例。

10.1.实现实体回调

EntityCallback 通过其通用类型参数直接与其域类型相关联。每个 Spring Data 模块通常附带一组预定义的 EntityCallback 接口,涵盖实体生命周期。

示例 83.EntityCallback 的剖析
@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked before a domain object is saved.
	 * Can return either the same or a modified instance.
	 *
	 * @return the domain object to be persisted.
	 */
	T onBeforeSave(T entity <2>, String collection <3>); (1)
}
 
1 BeforeSaveCallback 在保存实体之前要调用的特定方法。返回一个可能修改过的实例。
2 持久化之前的实体。
3 许多特定于存储的参数,例如实体被持久化到的 collection
示例 84. 响应式 EntityCallback 的剖析
@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {

	/**
	 * Entity callback method invoked on subscription, before a domain object is saved.
	 * The returned Publisher can emit either the same or a modified instance.
	 *
	 * @return Publisher emitting the domain object to be persisted.
	 */
	Publisher<T> onBeforeSave(T entity <2>, String collection <3>); (1)
}
 
1 BeforeSaveCallback 在保存实体之前调用订阅的特定方法。发出一个可能修改过的实例。
2 持久化之前的实体。
3 许多特定于存储的参数,例如实体被持久化到的 collection
可选的实体回调参数由实现 Spring Data 模块定义,并从 EntityCallback.callback() 的调用站点推断出来。

实现适合您的应用程序需求的接口,如下例所示:

示例 85. 示例BeforeSaveCallback
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {   (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {          (1)

		if(collection == "user") {
		  return // ...
		}

		return // ...
	}

	@Override
	public int getOrder() {
		return 100;                                 (2)
	}
}
 
1 根据您的要求执行回调。
2 如果存在多个针对同一域类型的实体回调,则可能对实体回调进行排序。排序遵循最低优先级。

10.2.注册实体回调

EntityCallback bean 由存储特定实现拾取,以防它们在 ApplicationContext 中注册。大多数模板 API 已经实现了 ApplicationContextAware,因此可以访问 ApplicationContext

以下示例解释了一组有效的实体回调注册:

示例 86. 示例 EntityCallback Bean 注册
@Order(1)                              (1)
@Component
class First implements BeforeSaveCallback<Person> {

	@Override
	public Person onBeforeSave(Person person) {
		return // ...
	}
}

@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
                              Ordered { (2)

	@Override
	public Object onBeforeSave(Person entity, String collection) {
		// ...
	}

	@Override
	public int getOrder() {
		return 100;                         (2)
	}
}

@Configuration
public class EntityCallbackConfiguration {

  @Bean
  BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {  (3)
    return (BeforeSaveCallback<Person>) it -> // ...
  }
}

@Component
class UserCallbacks implements BeforeConvertCallback<User>,
                    BeforeSaveCallback<User> {  (4)

	@Override
	public Person onBeforeConvert(User user) {
		return // ...
	}

	@Override
	public Person onBeforeSave(User user) {
		return // ...
	}
}
 
1 BeforeSaveCallback@Order 注解接收其命令。
2 BeforeSaveCallback 通过 Ordered 接口实现接收订单。
3 BeforeSaveCallback 使用 lambda 表达式。默认情况下无序并最后调用。请注意,由 lambda 表达式实现的回调不会公开类型信息,因此使用不可分配的实体调用这些回调会影响回调吞吐量。使用 classenum 为回调 bean 启用类型过滤。
4 在单个实现类中组合多个实体回调接口。

10.3. Elasticsearch 实体回调

Spring Data Elasticsearch 在内部使用 EntityCallback API 来支持审计,并对以下回调做出响应式:

表 4. 支持的实体回调
打回来 方法 描述 命令

响应式/BeforeConvertCallback

onBeforeConvert(T entity, IndexCoordinates index)

在域对象转换为 org.springframework.data.elasticsearch.core.document.Document 之前调用。可以返回 entity 或随后将被转换的修改后的实体。

Ordered.LOWEST_PRECEDENCE

响应式/AfterLoadCallback

onAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates)

在将 Elasticsearch 的结果读入 org.springframework.data.elasticsearch.core.document.Document 后调用。

Ordered.LOWEST_PRECEDENCE

响应式/AfterConvertCallback

onAfterConvert(T entity, Document document, IndexCoordinates indexCoordinates)

在从 Elasticsearch 读取结果数据时从 org.springframework.data.elasticsearch.core.document.Document 转换域对象后调用。

Ordered.LOWEST_PRECEDENCE

响应式/审计实体回调

onBeforeConvert(Object entity, IndexCoordinates index)

标记一个可审计的实体createdmodified

100

响应式/AfterSaveCallback

T onAfterSave(T entity, IndexCoordinates index)

在保存域对象后调用。

Ordered.LOWEST_PRECEDENCE

11. Join-Type实现

Spring Data Elasticsearch 支持连接数据类型 用于创建相应的索引映射并存储相关信息。

11.1.设置数据

对于要在父子连接关系中使用的实体,它必须具有类型为 JoinField 的属性,该属性必须被注解。让我们假设一个 Statement 实体,其中语句可能是一个 question 、一个 answer 、一个 comment 或一个 vote (这个例子中也显示了一个 Builder ,它不是必需的,但稍后在示例代码中使用):

@Document(indexName = "statements")
@Routing("routing")                                    (1)
public class Statement {
  @Id
  private String id;

  @Field(type = FieldType.Text)
  private String text;

  @Field(type = FieldType.Keyword)
  private String routing;

  @JoinTypeRelations(
    relations =
      {
        @JoinTypeRelation(parent = "question", children = {"answer", "comment"}), (2)
        @JoinTypeRelation(parent = "answer", children = "vote")          (3)
      }
  )
  private JoinField<String> relation;                          (4)

  private Statement() {
  }

  public static StatementBuilder builder() {
    return new StatementBuilder();
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getRouting() {
    return routing;
  }

  public void setRouting(Routing routing) {
    this.routing = routing;
  }

  public String getText() {
    return text;
  }

  public void setText(String text) {
    this.text = text;
  }

  public JoinField<String> getRelation() {
    return relation;
  }

  public void setRelation(JoinField<String> relation) {
    this.relation = relation;
  }

  public static final class StatementBuilder {
    private String id;
    private String text;
    private String routing;
    private JoinField<String> relation;

    private StatementBuilder() {
    }

    public StatementBuilder withId(String id) {
      this.id = id;
      return this;
    }

    public StatementBuilder withRouting(String routing) {
      this.routing = routing;
      return this;
    }

    public StatementBuilder withText(String text) {
      this.text = text;
      return this;
    }

    public StatementBuilder withRelation(JoinField<String> relation) {
      this.relation = relation;
      return this;
    }

    public Statement build() {
      Statement statement = new Statement();
      statement.setId(id);
      statement.setRouting(routing);
      statement.setText(text);
      statement.setRelation(relation);
      return statement;
    }
  }
}
 
1 有关路由相关信息,请参阅路由值
2 一个问题可以有答案和评论
3 一个答案可以有投票
4 JoinField 属性用于将关系的名称(questionanswercommentvote)与父 id 结合起来。泛型类型必须与 @Id 注解属性相同。

Spring Data Elasticsearch 将为此类构建以下映射:

{
 "statements": {
  "mappings": {
   "properties": {
    "_class": {
     "type": "text",
     "fields": {
      "keyword": {
       "type": "keyword",
       "ignore_above": 256
      }
     }
    },
    "routing": {
     "type": "keyword"
    },
    "relation": {
     "type": "join",
     "eager_global_ordinals": true,
     "relations": {
      "question": [
       "answer",
       "comment"
      ],
      "answer": "vote"
     }
    },
    "text": {
     "type": "text"
    }
   }
  }
 }
} 

11.2.存储数据

给定此类的存储库,以下代码插入一个问题、两个答案、一条评论和一个投票:

void init() {
  repository.deleteAll();

  Statement savedWeather = repository.save(
    Statement.builder()
      .withText("How is the weather?")
      .withRelation(new JoinField<>("question"))             (1)
      .build());

  Statement sunnyAnswer = repository.save(
    Statement.builder()
      .withText("sunny")
      .withRelation(new JoinField<>("answer", savedWeather.getId()))   (2)
      .build());

  repository.save(
    Statement.builder()
      .withText("rainy")
      .withRelation(new JoinField<>("answer", savedWeather.getId()))   (3)
      .build());

  repository.save(
    Statement.builder()
      .withText("I don't like the rain")
      .withRelation(new JoinField<>("comment", savedWeather.getId()))   (4)
      .build());

  repository.save(
    Statement.builder()
      .withText("+1 for the sun")
      ,withRouting(savedWeather.getId())
      .withRelation(new JoinField<>("vote", sunnyAnswer.getId()))     (5)
      .build());
}
 
1 创建一个问题陈述
2 问题的第一个答案
3 第二个答案
4 对问题的评论
5 为第一个答案投票,这需要将路由设置为天气文档,请参见路由值

11.3.检索数据

目前必须使用原生查询来查询数据,因此没有标准存储库方法的支持。 Spring Data Repository 的自定义实现 可以改用。

以下代码作为示例显示了如何使用 ElasticsearchOperations 实例检索具有 vote(必须是 answers,因为只有答案可以投票)的所有条目:

SearchHits<Statement> hasVotes() {

	Query query = NativeQuery.builder()
		.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb
			.hasChild(hc -> hc
				.queryName("vote")
				.query(matchAllQueryAsQuery())
				.scoreMode(ChildScoreMode.None)
			)))
		.build();

	return operations.search(query, Statement.class);
}
 

12.路由值

当 Elasticsearch 将文档存储在具有多个分片的索引中时,它会根据文档的id 来确定您使用的分片。有时需要预先定义多个文档应该在同一个分片上建立索引(连接类型,更快地搜索相关数据)。为此,Elasticsearch 提供了定义路由的可能性,路由是应该用于计算分片的值,而不是 id

Spring Data Elasticsearch 支持通过以下方式存储和检索数据的路由定义:

12.1.连接类型的路由

使用连接类型时(请参阅 连接类型实现 ),Spring Data Elasticsearch 将自动使用实体的 JoinField 属性的 parent 属性作为路由的值。

这对于父子关系只有一个级别的所有用例都是正确的。如果它更深,比如子-父母-祖父母关系 - 如上例中的 voteanswerquestion - 然后需要使用下一节中描述的技术明确指定路由(vote 需要 question.id 作为路由值)。

12.2.自定义路由值

为了为实体定义自定义路由,Spring Data Elasticsearch 提供了一个 @Routing 注解(重用上面的 Statement 类):

@Document(indexName = "statements")
@Routing("routing")         (1)
public class Statement {
  @Id
  private String id;

  @Field(type = FieldType.Text)
  private String text;

  @JoinTypeRelations(
    relations =
      {
        @JoinTypeRelation(parent = "question", children = {"answer", "comment"}),
        @JoinTypeRelation(parent = "answer", children = "vote")
      }
  )
  private JoinField<String> relation;

  @Nullable
  @Field(type = FieldType.Keyword)
  private String routing;     (2)

  // getter/setter...
}
 
1 这将 "routing" 定义为路由规范
2 名为 routing 的属性

如果注解的 routing 规范是纯字符串而不是 SpEL 表达式,则它被解释为实体属性的名称,在示例中它是 routing 属性。然后,此属性的值将用作使用该实体的所有请求的路由值。

也可以在 @Document 注解中使用 SpEL 表达式,如下所示:

@Document(indexName = "statements")
@Routing("@myBean.getRouting(#entity)")
public class Statement{
  // all the needed stuff
}
 

在这种情况下,用户需要提供一个名为 myBean 且具有方法 String getRouting(Object) 的 bean。要引用实体 "#entity" 必须在 SpEL 表达式中使用,并且返回值必须是 null 或作为字符串的路由值。

如果普通属性的名称和 SpEL 表达式不足以自定义路由定义,则可以定义提供 RoutingResolver 接口的实现。然后可以在 ElasticOperations 实例上设置:

RoutingResolver resolver = ...;

ElasticsearchOperations customOperations= operations.withRouting(resolver);
 

withRouting() 函数返回带有自定义路由集的原始 ElasticsearchOperations 实例的副本。

如果在 Elasticsearch 中存储实体时定义了路由,则在执行 getdelete 操作时必须提供相同的值。对于不使用实体的方法 - 如 get(ID)delete(ID) - ElasticsearchOperations.withRouting(RoutingResolver) 方法可以这样使用:

String id = "someId";
String routing = "theRoutingValue";

// get an entity
Statement s = operations
        .withRouting(RoutingResolver.just(routing))    (1)
        .get(id, Statement.class);

// delete an entity
operations.withRouting(RoutingResolver.just(routing)).delete(id);
 
1 RoutingResolver.just(s) 返回一个只返回给定字符串的解析器。

13.杂项Elasticsearch操作支持

本章涵盖了对无法通过存储库接口直接访问的 Elasticsearch 操作的额外支持。建议将这些操作添加为自定义实现,如 Spring Data Repository 的自定义实现 中所述。

13.1.索引设置

使用 Spring Data Elasticsearch 创建 Elasticsearch 索引时,可以使用 @Setting 注解定义不同的索引设置。以下参数可用:

  • useServerConfiguration 不发送任何设置参数,因此 Elasticsearch 服务配置决定了它们。

  • settingPath 指的是定义必须在类路径中可解析的设置的 JSON 文件

  • shards 使用的分片数量,默认为 1

  • replicas副本数,默认为1

  • refreshIntervall ,默认为 "1s"

  • indexStoreType ,默认为 "fs"

也可以定义 索引排序(检查链接的 Elasticsearch 文档以了解可能的字段类型和值):

@Document(indexName = "entities")
@Setting(
 sortFields = { "secondField", "firstField" },                 (1)
 sortModes = { Setting.SortMode.max, Setting.SortMode.min },          (2)
 sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc },
 sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first })
class Entity {
  @Nullable
  @Id private String id;

  @Nullable
  @Field(name = "first_field", type = FieldType.Keyword)
  private String firstField;

  @Nullable @Field(name = "second_field", type = FieldType.Keyword)
  private String secondField;

  // getter and setter...
}
 
1 定义排序字段时,使用 Java 属性的名称 (firstField),而不是可能为 Elasticsearch 定义的名称 (first_field)
2 sortModessortOrderssortMissingValues 是可选的,但如果设置了它们,条目数必须与 sortFields 元素数匹配

13.2.索引映射

当 Spring Data Elasticsearch 使用 IndexOperations.createMapping() 方法创建索引映射时,它使用 映射注解概述 中描述的注解,尤其是 @Field 注解。除此之外,还可以将 @Mapping 注解添加到类中。此注解具有以下属性:

  • mappingPath JSON 格式的类路径资源;如果这不为空,则将其用作映射,不进行其他映射处理。

  • enabled 当设置为 false 时,此标志将写入映射并且不会进行进一步处理。

  • dateDetectionnumericDetection 在未设置为 DEFAULT 时设置映射中的相应属性。

  • dynamicDateFormats 当这个字符串数组不为空时,它定义了用于自动日期检测的日期格式。

  • runtimeFieldsPath JSON 格式的类路径资源,包含写入索引映射的运行时字段的定义,例如:

{
 "day_of_week": {
  "type": "keyword",
  "script": {
   "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
  }
 }
} 

13.3.过滤器生成器

Filter Builder 提高了查询速度。

private ElasticsearchOperations operations;

IndexCoordinates index = IndexCoordinates.of("sample-index");

Query query = NativeQuery.builder()
	.withQuery(q -> q
		.matchAll(ma -> ma))
	.withFilter( q -> q
		.bool(b -> b
			.must(m -> m
				.term(t -> t
					.field("id")
					.value(documentId))
			)))
	.build();

SearchHits<SampleEntity> sampleEntities = operations.search(query, SampleEntity.class, index);
 

13.4.对大结果集使用滚动

Elasticsearch 有一个滚动 API,用于以块的形式获取大结果集。 Spring Data Elasticsearch 在内部使用它来提供 <T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index) 方法的实现。

IndexCoordinates index = IndexCoordinates.of("sample-index");

Query searchQuery = NativeQuery.builder()
  .withQuery(q -> q
    .matchAll(ma -> ma))
  .withFields("message")
  .withPageable(PageRequest.of(0, 10))
  .build();

SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
index);

List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
 sampleEntities.add(stream.next());
}

stream.close();
 

SearchOperations API 中没有访问滚动 id 的方法,如果有必要访问它,可以使用 AbstractElasticsearchTemplate 的以下方法(这是不同 ElasticsearchOperations 实现的基本实现):

@Autowired ElasticsearchOperations operations;

AbstractElasticsearchTemplate template = (AbstractElasticsearchTemplate)operations;

IndexCoordinates index = IndexCoordinates.of("sample-index");

Query query = NativeQuery.builder()
  .withQuery(q -> q
    .matchAll(ma -> ma))
  .withFields("message")
  .withPageable(PageRequest.of(0, 10))
  .build();

SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, query, SampleEntity.class, index);

String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasSearchHits()) {
 sampleEntities.addAll(scroll.getSearchHits());
 scrollId = scroll.getScrollId();
 scroll = template.searchScrollContinue(scrollId, 1000, SampleEntity.class);
}
template.searchScrollClear(scrollId);
 

要将 Scroll API 与存储库方法一起使用,返回类型必须在 Elasticsearch 存储库中定义为 Stream。该方法的实现将使用 ElasticsearchTemplate 中的滚动方法。

interface SampleEntityRepository extends Repository<SampleEntity, String> {

  Stream<SampleEntity> findBy();

}
 

13.5.排序选项

除了 分页和排序 中描述的默认排序选项之外,Spring Data Elasticsearch 还提供了派生自 org.springframework.data.domain.Sort.Order 的类 org.springframework.data.elasticsearch.core.query.Order。它提供了在指定结果排序时可以发送到 Elasticsearch 的附加参数(请参阅 https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html )。

还有一个 org.springframework.data.elasticsearch.core.query.GeoDistanceOrder 类,可用于获得按地理距离排序的搜索操作的结果。

如果要检索的类具有名为 locationGeoPoint 属性,则以下 Sort 将按到给定点的距离对结果进行排序:

Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
 

13.6.运行时字段

从 7.12 版开始,Elasticsearch 添加了运行时字段 (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html) 的功能。 Spring Data Elasticsearch 通过两种方式支持这一点:

13.6.1.索引映射中的运行时字段定义

定义运行时字段的第一种方法是将定义添加到索引映射(参见 https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html )。要在 Spring Data Elasticsearch 中使用此方法,用户必须提供包含相应定义的 JSON 文件,例如:

示例 87.runtime-fields.json
{
 "day_of_week": {
  "type": "keyword",
  "script": {
   "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
  }
 }
} 

这个 JSON 文件的路径必须出现在类路径中,然后必须在实体的 @Mapping 注解中设置:

@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/runtime-fields.json")
public class RuntimeFieldEntity {
	// properties, getter, setter,...
}
 

13.6.2.在查询上设置的运行时字段定义

定义运行时字段的第二种方法是将定义添加到搜索查询中(请参阅 https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html )。以下代码示例显示了如何使用 Spring Data Elasticsearch 执行此操作:

使用的实体是一个具有price属性的简单对象:

@Document(indexName = "some_index_name")
public class SomethingToBuy {

	private @Id @Nullable String id;
	@Nullable @Field(type = FieldType.Text) private String description;
	@Nullable @Field(type = FieldType.Double) private Double price;

	// getter and setter
}
 

以下查询使用一个运行时字段,该字段通过将价格增加 19% 来计算 priceWithTax 值,并在搜索查询中使用该值来查找 priceWithTax 高于或等于给定值的所有实体:

RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
query.addRuntimeField(runtimeField);

SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
 

这适用于 Query 接口的每个实现。

13.7.时间点 (PIT) API

ElasticsearchOperations 支持 Elasticsearch 的时间点 API(参见 https://www.elastic.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html )。以下代码片段显示了如何将此功能用于虚构的 Person 类:

ElasticsearchOperations operations; // autowired
Duration tenSeconds = Duration.ofSeconds(10);

String pit = operations.openPointInTime(IndexCoordinates.of("person"), tenSeconds); (1)

// create query for the pit
Query query1 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Smith"))
  .withPointInTime(new Query.PointInTime(pit, tenSeconds))            (2)
  .build();
SearchHits<Person> searchHits1 = operations.search(query1, Person.class);
// do something with the data

// create 2nd query for the pit, use the id returned in the previous result
Query query2 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Miller"))
  .withPointInTime(
    new Query.PointInTime(searchHits1.getPointInTimeId(), tenSeconds))     (3)
  .build();
SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
// do something with the data

operations.closePointInTime(searchHits2.getPointInTimeId());            (4)
 
1 为索引创建一个时间点(可以是多个名称)和一个保持活动的持续时间并检索它的 id
2 将该 id 传递到查询中以与下一个 keep-alive 值一起搜索
3 对于下一个查询,使用从上一个搜索返回的 id
4 完成后,使用最后返回的 id 关闭时间点

13.8.搜索模板支持

支持使用搜索模板 API。要使用它,首先需要创建一个存储脚本。 ElasticsearchOperations 接口扩展了 ScriptOperations,它提供了必要的功能。此处使用的示例假设我们有 Person 实体,其属性名为 firstName。搜索模板脚本可以这样保存:

import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.script.Script;

operations.putScript(              (1)
 Script.builder()
  .withId("person-firstname")         (2)
  .withLanguage("mustache")          (3)
  .withSource("""               (4)
   {
    "query": {
     "bool": {
      "must": [
       {
        "match": {
         "firstName": "{{firstName}}"  (5)
        }
       }
      ]
     }
    },
    "from": "{{from}}",           (6)
    "size": "{{size}}"            (7)
   }
   """)
  .build()
);
 
1 使用putScript()方法存储一个搜索模板脚本
2 脚本的名称/id
3 搜索模板中使用的脚本必须使用 mustache 语言。
4 脚本来源
5 脚本中的搜索参数
6 分页请求偏移量
7 分页请求大小

为了在搜索查询中使用搜索模板,Spring Data Elasticsearch 提供了 SearchTemplateQuery ,这是 org.springframework.data.elasticsearch.core.query.Query 接口的实现。

在下面的代码中,我们将使用搜索模板查询添加一个调用到自定义存储库实现(请参阅 Spring Data Repository 的自定义实现 ),作为如何将其集成到存储库调用中的示例。

我们首先定义自定义存储库片段接口:

interface PersonCustomRepository {
	SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable);
}
 

此存储库片段的实现如下所示:

public class PersonCustomRepositoryImpl implements PersonCustomRepository {

 private final ElasticsearchOperations operations;

 public PersonCustomRepositoryImpl(ElasticsearchOperations operations) {
  this.operations = operations;
 }

 @Override
 public SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable) {

  var query = SearchTemplateQuery.builder()                (1)
   .withId("person-firstname")                      (2)
   .withParams(
    Map.of(                               (3)
     "firstName", firstName,
     "from", pageable.getOffset(),
     "size", pageable.getPageSize()
     )
   )
   .build();

  SearchHits<Person> searchHits = operations.search(query, Person.class); (4)

  return SearchHitSupport.searchPageFor(searchHits, pageable);
 }
}
 
1 创建一个SearchTemplateQuery
2 提供搜索模板的 ID
3 参数在Map<String,Object>中传递
4 以与其他查询类型相同的方式进行搜索。

附录

附录 A:命名空间参考

<repositories /> 元素

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

表 5. 属性
名称 描述

base-package

定义要在自动检测模式下为扩展*Repository(实际接口由特定 Spring Data 模块确定)的存储库接口扫描的包。配置包下的所有包也被扫描。允许使用通配符。

repository-impl-postfix

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

query-lookup-strategy

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

named-queries-location

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

consider-nested-repositories

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

附录 B:Populators 命名空间参考

<populator /> 元素

<populator /> 元素允许通过 Spring Data 存储库基础结构填充数据存储。[2]

表 6. 属性
名称 描述

locations

在哪里可以找到从存储库中读取对象的文件。

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

支持的查询方式主题关键词

下表列出了Spring Data Repository查询推导机制普遍支持的主语关键字来表达谓语。请查阅特定于存储的文档以获取受支持关键字的确切列表,因为特定存储可能不支持此处列出的某些关键字。

表 7. 查询主题关键字
关键词 描述

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

一般查询方法通常返回存储库类型、CollectionStreamable 子类型或结果包装器,例如 PageGeoResults 或任何其他存储特定的结果包装器。可用作 findBy…findMyDomainTypeBy… 或与其他关键字结合使用。

exists…By

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

count…By

返回数字结果的计数投影。

delete…By , remove…By

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

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

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

…Distinct…

使用不同的查询只返回唯一的结果。请查阅特定于存储的文档是否支持该功能。此关键字可以出现在主题的任何位置,介于 find(和其他关键字)和 by 之间。

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

下表列出了 Spring Data 存储库查询派生机制普遍支持的谓词关键字。但是,请查阅特定于存储的文档以获取受支持关键字的确切列表,因为特定存储可能不支持此处列出的某些关键字。

表 8. 查询谓词关键字
逻辑关键字 关键字表达式

AND

And

OR

Or

AFTER

After , IsAfter

BEFORE

Before , IsBefore

CONTAINING

Containing , IsContaining , Contains

BETWEEN

Between , IsBetween

ENDING_WITH

EndingWith , IsEndingWith , EndsWith

EXISTS

Exists

FALSE

False , IsFalse

GREATER_THAN

GreaterThan , IsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqual , IsGreaterThanEqual

IN

In , IsIn

IS

Is , Equals , (或无关键字)

IS_EMPTY

IsEmpty , Empty

IS_NOT_EMPTY

IsNotEmpty , NotEmpty

IS_NOT_NULL

NotNull , IsNotNull

IS_NULL

Null , IsNull

LESS_THAN

LessThan , IsLessThan

LESS_THAN_EQUAL

LessThanEqual , IsLessThanEqual

LIKE

Like , IsLike

NEAR

Near , IsNear

NOT

Not , IsNot

NOT_IN

NotIn , IsNotIn

NOT_LIKE

NotLike , IsNotLike

REGEX

Regex , MatchesRegex , Matches

STARTING_WITH

StartingWith , IsStartingWith , StartsWith

TRUE

True , IsTrue

WITHIN

Within , IsWithin

除了过滤谓词之外,还支持以下修饰符列表:

表 9. 查询谓词修饰符关键字
关键词 描述

IgnoreCase , IgnoringCase

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

AllIgnoreCase , AllIgnoringCase

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

OrderBy…

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

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

支持的查询返回类型

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

地理空间类型(例如 GeoResultGeoResultsGeoPage )仅适用于支持地理空间查询的数据存储。一些存储模块可能会定义自己的结果包装器类型。
表 10. 查询返回类型
返回类型 描述

void

表示没有返回值。

Primitives

Java原语。

Wrapper types

Java 包装器类型。

T

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

Iterator<T>

一个Iterator

Collection<T>

一个Collection

List<T>

一个List

Optional<T>

Java 8 或番石榴 Optional 。期望查询方法最多返回一个结果。如果未找到结果,则返回 Optional.empty()Optional.absent()。不止一个结果会触发 IncorrectResultSizeDataAccessException

Option<T>

Scala 或 Vavr Option 类型。在语义上与前面描述的 Java 8 的 Optional 行为相同。

Stream<T>

Java 8 Stream

Streamable<T>

Iterable 的便利扩展,它直接公开流、映射和过滤结果的方法,连接它们等。

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

公开构造函数或 ….of(…) /….valueOf(…)Streamable 作为参数的工厂方法的类型。有关详细信息,请参阅 返回自定义 Streamable Wrapper 类型

Vavr Seq, List, Map, Set

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

Future<T>

一个Future。期望一个方法用@Async注解,需要开启Spring的异步方法执行能力。

CompletableFuture<T>

Java 8 CompletableFuture 。期望一个方法用@Async注解,需要开启Spring的异步方法执行能力。

Slice<T>

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

Page<T>

带有附加信息的 Slice,例如结果总数。需要一个 Pageable 方法参数。

GeoResult<T>

带有附加信息的结果条目,例如到参考位置的距离。

GeoResults<T>

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

GeoPage<T>

PageGeoResult<T>,例如到参考位置的平均距离。

Mono<T>

Project Reactor Mono 使用响应式性存储库发射零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回 Mono.empty()。不止一个结果会触发 IncorrectResultSizeDataAccessException

Flux<T>

Project Reactor Flux 使用响应式性存储库发射零个、一个或多个元素。返回 Flux 的查询也可以发出无限数量的元素。

Single<T>

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

Maybe<T>

一个 RxJava Maybe 使用响应式性存储库发出零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回 Mono.empty()。不止一个结果会触发 IncorrectResultSizeDataAccessException

Flowable<T>

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

附录 E:迁移指南

从 3.2.x 升级到 4.0.x

本节介绍从版本 3.2.x 到 4.0.x 的重大更改,以及如何用新引入的功能替换已删除的功能。

拆除使用过的 Jackson Mapper

4.0.x 版中的变化之一是 Spring Data Elasticsearch 不再使用 Jackson Mapper 将实体映射到 Elasticsearch 所需的 JSON 表示(请参阅 Elasticsearch 对象映射 )。在 3.2.x 版中,Jackson Mapper 是默认使用的。可以通过显式配置 (元模型对象映射) 切换到基于元模型的转换器(名为 ElasticsearchEntityMapper)。

在 4.0.x 版中,基于元模型的转换器是唯一可用且不需要显式配置的转换器。如果您有一个自定义配置来通过提供这样的 bean 来启用元模型转换器:

@Bean
@Override
public EntityMapper entityMapper() {

 ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
  elasticsearchMappingContext(), new DefaultConversionService()
 );
 entityMapper.setConversions(elasticsearchCustomConversions());

 return entityMapper;
}
 

您现在必须删除这个 bean,ElasticsearchEntityMapper 接口已被删除。

实体配置

一些用户在实体类上有自定义 Jackson 注解,例如,为了在 Elasticsearch 中为映射文档定义自定义名称或配置日期转换。这些不再考虑。所需的功能现在由 Spring Data Elasticsearch 的 @Field 注解提供。请参阅 映射注解概述 了解详细信息。

从查询对象中删除隐式索引名称

在 3.2.x 中,不同的查询类(如 IndexQuerySearchQuery)具有采用索引名称或它们正在操作的索引名称的属性。如果未设置这些,则检查传入的实体以检索在 @Document 注解中设置的索引名称。
在 4.0.x 中,现在必须在 IndexCoordinates 类型的附加参数中提供索引名称。通过将其分开,现在可以针对不同的索引使用一个查询对象。

因此,例如以下代码:

IndexQuery indexQuery = new IndexQueryBuilder()
 .withId(person.getId().toString())
 .withObject(person)
 .build();

String documentId = elasticsearchOperations.index(indexQuery);
 

必须改为:

IndexCoordinates indexCoordinates = elasticsearchOperations.getIndexCoordinatesFor(person.getClass());

IndexQuery indexQuery = new IndexQueryBuilder()
 .withId(person.getId().toString())
 .withObject(person)
 .build();

String documentId = elasticsearchOperations.index(indexQuery, indexCoordinates);
 

为了更轻松地处理实体并使用包含在实体的 @Document 注解中的索引名称,添加了新方法,如 DocumentOperations.save(T entity)

新的操作接口

在 3.2 版中,有一个 ElasticsearchOperations 接口定义了 ElasticsearchTemplate 类的所有方法。在版本 4 中,功能已拆分为不同的接口,使这些接口与 Elasticsearch API 保持一致:

  • DocumentOperations 是与文档相关的功能,如保存或删除

  • SearchOperations 包含在 Elasticsearch 中搜索的函数

  • IndexOperations 定义对索引进行操作的函数,例如索引创建或映射创建。

ElasticsearchOperations 现在扩展了 DocumentOperationsSearchOperations 并且有方法可以访问 IndexOperations 实例。

3.2 版 ElasticsearchOperations 接口中现在移至 IndexOperations 接口的所有功能仍然可用,它们被标记为已弃用,并具有委托给新实现的默认实现:
/**
 * Create an index for given indexName.
 *
 * @param indexName the name of the index
 * @return {@literal true} if the index was created
 * @deprecated since 4.0, use {@link IndexOperations#create()}
 */
@Deprecated
default boolean createIndex(String indexName) {
	return indexOps(IndexCoordinates.of(indexName)).create();
}
 

弃用

方法和类

许多函数和类已被弃用。这些功能仍然有效,但 Javadocs 显示了它们应该被替换的内容。

来自 ElasticsearchOperations 的示例
/*
 * Retrieves an object from an index.
 *
 * @param query the query defining the id of the object to get
 * @param clazz the type of the object to be returned
 * @return the found object
 * @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
 */
@Deprecated
@Nullable
<T> T queryForObject(GetQuery query, Class<T> clazz);
 
Elasticsearch 弃用

自版本 7 以来,Elasticsearch TransportClient 已弃用,它将随 Elasticsearch 版本 8 一起删除。Spring Data Elasticsearch 弃用了 ElasticsearchTemplate 类,它在版本 4.0 中使用了 TransportClient

映射类型已从 Elasticsearch 7 中删除,它们在 Spring Data @Document 注解和 IndexCoordinates 类中仍然作为弃用值存在,但在内部不再使用。

删除

  • 如前所述,ElasticsearchEntityMapper 接口已被删除。

  • SearchQuery 接口已合并到它的基础接口 Query 中,因此它的出现可以用 Query 替换。

  • 方法 org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);org.springframework.data.elasticsearch.core.ResultsExtractor 接口已被删除。这些可用于解析来自 Elasticsearch 的结果,以解决使用基于 Jackson 的映射器完成的响应映射不够的情况。从 4.0 版开始,有新的 搜索结果类型 可以从 Elasticsearch 响应中返回信息,因此无需公开此低级功能。

  • 低级方法 startScrollcontinueScrollclearScroll 已从 ElasticsearchOperations 接口中删除。对于低级滚动 API 访问,ElasticsearchRestTemplate 类现在有 searchScrollStartsearchScrollContinuesearchScrollClear 方法。

从 4.0.x 升级到 4.1.x

本节介绍从版本 4.0.x 到 4.1.x 的重大更改,以及如何用新引入的功能替换已删除的功能。

弃用

id 属性的定义

可以通过将 en 实体的属性命名为 iddocument 来将其定义为 id 属性。此行为现已弃用,并将产生警告。请使用 @Id 注解将属性标记为 id 属性。

索引映射

ReactiveElasticsearchClient.Indices 接口中,updateMapping 方法已弃用,取而代之的是 putMapping 方法。他们做的一样,但是putMapping和Elasticsearch API中的命名是一致的:

别名处理

IndexOperations 接口中,方法 addAlias(AliasQuery)removeAlias(AliasQuery)queryForAlias() 已被弃用。新方法 alias(AliasAction)getAliases(String…​)getAliasesForIndex(String…​) 提供更多功能和更简洁的 API。

家长编号

从版本 6 开始,父 ID 的使用已从 Elasticsearch 中删除。我们现在弃用了相应的字段和方法。

移除

类型映射

@Document 注解和 IndexCoordinates 对象的 type mappings 参数已删除。它们在 Spring Data Elasticsearch 4.0 中已被弃用,并且不再使用它们的值。

重大变更

ReactiveElasticsearchClient.Indices 方法的返回类型

ReactiveElasticsearchClient.Indices中的方法到现在都没用到。随着 ReactiveIndexOperations 的引入,有必要更改一些返回类型:

  • createIndex 变体现在返回 Mono<Boolean> 而不是 Mono<Void> 以表示索引创建成功。

  • updateMapping 变体现在返回 Mono<Boolean> 而不是 Mono<Void> 以表示映射存储成功。

DocumentOperations.bulkIndex 方法的返回类型

这些方法返回一个包含新索引记录 ID 的 List<String>。现在他们返回一个 List<IndexedObjectInformation> ;这些对象包含有关乐观锁的 id 和信息(seq_no 和 primary_term)

从 4.1.x 升级到 4.2.x

本节介绍从版本 4.1.x 到 4.2.x 的重大更改以及如何用新引入的功能替换已删除的功能。

弃用

@文档参数

与索引设置相关的 @Document 注解参数(useServerConfigurationshardsreplicasrefreshIntervallindexStoretype)已移至 @Setting 注解。在 @Document 中使用仍然可行,但已弃用。

移除

用于在实体中设置得分返回值的 @Score 注解在 4.0 版中已弃用并已被删除。得分值在封装返回实体的 SearchHit 实例中返回。

org.springframework.data.elasticsearch.ElasticsearchException 类已被删除。其余用法已替换为 org.springframework.data.mapping.MappingExceptionorg.springframework.dao.InvalidDataAccessApiUsageException

已弃用的 ScoredPageScrolledPage@AggregatedPage 和实现已被删除。

已弃用的 GetQueryDeleteQuery 已被删除。

ReactiveSearchOperationsReactiveDocumentOperations 中已弃用的 find 方法已被删除。

重大变更

刷新策略
枚举包已更改

在 4.1 中可以通过覆盖自定义配置类中的方法 AbstractReactiveElasticsearchConfiguration.refreshPolicy() 来配置 ReactiveElasticsearchTemplate 的刷新策略。此方法的返回值是类 org.elasticsearch.action.support.WriteRequest.RefreshPolicy 的一个实例。

现在配置必须返回 org.springframework.data.elasticsearch.core.RefreshPolicy 。此枚举具有与以前相同的值并触发相同的行为,因此只需调整 import 语句。

刷新行为

ElasticsearchOperationsReactiveElasticsearchOperations 现在显式使用模板上的 RefreshPolicy 集用于写入请求(如果不为空)。如果刷新策略为 null,则不会执行任何特殊操作,因此使用集群默认值。 ElasticsearchOperations 在此版本之前始终使用集群默认值。

ElasticsearchRepositoryReactiveElasticsearchRepository 提供的实现将在刷新策略为空时进行显式刷新。这与以前版本中的行为相同。如果设置了刷新策略,那么它也将被存储库使用。

刷新配置

当使用 ElasticsearchConfigurationSupportAbstractElasticsearchConfigurationAbstractReactiveElasticsearchConfigurationElasticsearch Clients 中描述的那样配置 Spring Data Elasticsearch 时,刷新策略将被初始化为 null 。以前响应式代码将其初始化为 IMMEDIATE ,现在响应式和非响应式代码显示相同的行为。

方法返回类型
删除采用查询的方法

响应式方法先前返回一个带有已删除文档数量的 Mono<Long>,非响应式版本无效。他们现在返回一个 Mono<ByQueryResponse>,其中包含有关已删除文档和可能发生的错误的更多详细信息。

多重获取方法

multiget previousl 的实现仅在 List<T> 中返回找到的实体用于非响应式性实现,在 Flux<T> 中用于响应式性实现。如果请求包含未找到的 ID,则这些缺失的信息不可用。用户需要将返回的 ID 与请求的 ID 进行比较,以找出缺少的 ID。

现在 multiget 方法为每个请求的 ID 返回一个 MultiGetItem。这包含有关失败的信息(如不存在的索引)以及项目是否存在的信息(然后它包含在 `MultiGetItem 中)。

从 4.2.x 升级到 4.3.x

本节介绍从版本 4.2.x 到 4.3.x 的重大更改,以及如何用新引入的功能替换已删除的功能。

Elasticsearch 正在开发一个新的客户端,它将取代 RestHighLevelClient,因为 RestHighLevelClient 使用 Elasticsearch 核心库中的代码,这些库不再获得 Apache 2 许可。 Spring Data Elasticsearch 也在为这种变化做准备。这意味着 *Operations 接口的内部实现需要更改 - 如果用户针对 ElasticsearchOperationsReactiveElasticsearchOperations 等接口进行编程,这应该没有问题。如果您直接使用像ElasticsearchRestTemplate这样的实现类,您将需要适应这些变化。

Spring Data Elasticsearch 还在其 API 类和方法中删除或替换了 org.elasticsearch 包中类的使用,仅在实现对 Elasticsearch 的访问的实现中使用它们。对于用户来说,这意味着一些使用过的枚举类被 org.springframework.data.elasticsearch 中具有相同值的枚举替换,这些枚举在内部映射到 Elasticsearch 类。

使用不易替换的类的地方,这种用法被标记为已弃用,我们正在努力进行替换。

查看 弃用Breaking Changes 部分了解更多详细信息。

弃用

建议方法

SearchOperations 以及 ElasticsearchOperations 中,以 org.elasticsearch.search.suggest.SuggestBuilder 作为参数并返回 org.elasticsearch.action.search.SearchResponsesuggest 方法已被弃用。使用 SearchHits<T> search(Query query, Class<T> clazz) 代替,传递一个 NativeSearchQuery 其中可以包含一个 SuggestBuilder 并从返回的 SearchHit<T> 读取建议结果。

ReactiveSearchOperations 中,新的 suggest 方法现在返回一个 Mono<org.springframework.data.elasticsearch.core.suggest.response.Suggest>。这里也弃用了旧方法。

重大变更

从 API 中删除了 org.elasticsearch 类。
  • org.springframework.data.elasticsearch.annotations.CompletionContext 注解中,属性 type() 已从 org.elasticsearch.search.suggest.completion.context.ContextMapping.Type 更改为 org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType,可用枚举值相同。

  • org.springframework.data.elasticsearch.annotations.Document 注解中, versionType() 属性已更改为 org.springframework.data.elasticsearch.annotations.Document.VersionType ,可用枚举值相同。

  • org.springframework.data.elasticsearch.core.query.Query 接口中,searchType() 属性已更改为 org.springframework.data.elasticsearch.core.query.Query.SearchType,可用枚举值相同。

  • org.springframework.data.elasticsearch.core.query.Query 接口中,timeout() 的返回值更改为 java.time.Duration

  • SearchHits<T>`class does not contain the `org.elasticsearch.search.aggregations.Aggregations 了。相反,它现在包含 org.springframework.data.elasticsearch.core.AggregationsContainer<T> 类的一个实例,其中 T 是所使用的底层客户端的具体聚合类型。目前这将是一个 org .springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations 对象;稍后将提供不同的实现。对 ReactiveSearchOperations.aggregate() 函数进行了相同的更改,现在返回一个 Flux<AggregationContainer<?>> 。需要更改使用聚合的程序以将返回值转换为适当的类以进一步处理它。

  • 可能抛出 org.elasticsearch.ElasticsearchStatusException 的方法现在将抛出 org.springframework.data.elasticsearch.RestStatusException

处理 Query 的字段和 sourceFilter 属性

直到版本 4.2,Queryfields 属性被解释并添加到 sourceFilter 的包含列表中。这是不正确的,因为这些对于 Elasticsearch 来说是不同的。这已得到纠正。因此,依赖于使用 fields 指定应从文档的 _source' and should be changed to use the `sourceFilter 返回哪些字段的代码可能不再起作用。

search_type 默认值

Elasticsearch 中 search_type 的默认值为 query_then_fetch 。这现在也被设置为 Query 实现中的默认值,它以前被设置为 dfs_query_then_fetch

批量选项更改

org.springframework.data.elasticsearch.core.query.BulkOptions 类的某些属性已更改其类型:

  • timeout 属性的类型已更改为 java.time.Duration

  • `refreshPolicy` 属性的类型已更改为 org.springframework.data.elasticsearch.core.RefreshPolicy

IndicesOptions 更改

Spring Data Elasticsearch 现在使用 org.springframework.data.elasticsearch.core.query.IndicesOptions 而不是 org.elasticsearch.action.support.IndicesOptions

完成类

org.springframework.data.elasticsearch.core.completion 中的类已移至 org.springframework.data.elasticsearch.core.suggest

其他重命名

org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter 接口已重命名为 org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter。同样,名为 XXPersistentPropertyConverter 的实现类已重命名为 XXPropertyValueConverter

从 4.3.x 升级到 4.4.x

本节介绍从版本 4.3.x 到 4.4.x 的重大更改以及如何用新引入的功能替换已删除的功能。

弃用

org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations

<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback) 方法已被弃用。由于现在有多个实现使用不同的客户端库,execute 方法在不同的实现中仍然可用,但接口中没有更多的方法,因为不同的客户端没有通用的回调接口。

重大变更

删除已弃用的类
org.springframework.data.elasticsearch.core.ElasticsearchTemplate 已被删除

从 4.4 版开始,Spring Data Elasticsearch 不再使用 Elasticsearch 中的 TransportClient(自 Elasticsearch 7.0 以来,它本身已被弃用)。这意味着自 Spring Data Elasticsearch 4.0 以来不推荐使用的 org.springframework.data.elasticsearch.core.ElasticsearchTemplate 类已被删除。这是使用 TransportClientElasticsearchOperations 接口的实现。必须使用命令式 ElasticsearchRestTemplate 或响应式 ReactiveElasticsearchTemplate 连接到 Elasticsearch。

包更改

在 4.3 中,两个类(ElasticsearchAggregationsElasticsearchAggregation)已移至 org.springframework.data.elasticsearch.core.clients.elasticsearch7 包中,以准备集成新的 Elasticsearch 客户端。它们被移回了 org.springframework.data.elasticsearch.core 包,因为我们让这些类在它们原来的位置使用旧的 Elasticsearch 客户端。

行为改变

ReactiveElasticsearchTemplate 在直接创建或由 Spring Boot 配置创建时具有默认刷新策略 IMMEDIATE。这可能会导致大量索引的性能问题,并且与 Elasticsearch 的默认行为不同。这已更改为现在默认刷新策略为无。当使用 响应式客户端(已弃用) 中描述的配置提供 ReactiveElasticsearchTemplate 时,默认刷新策略已设置为 NONE。

新的 Elasticsearch 客户端

Elasticsearch 推出了新的 ElasticsearchClient 并弃用了之前的 RestHighLevelClient 。 Spring Data Elasticsearch 4.4 仍然使用旧客户端作为默认客户端,原因如下:

  • 新客户端强制应用程序使用 jakarta.json.spi.JsonProvider 包,而 Spring Boot 将坚持使用 javax.json.spi.JsonProvider 直到版本 3.因此,在 Spring Data Elasticsearch 中切换默认实现只能随 Spring Data Elasticsearch 5(Spring Data 3、Spring 6)一起使用。

  • Elasticsearch客户端还有一些bug需要解决

  • 由于资源有限,在 Spring Data Elasticsearch 中使用新客户端的实现尚未完成 - 请记住,Spring Data Elasticsearch 是一个社区驱动的项目,依赖于公共贡献。

新客户端如何使用
使用新客户端的实现并不完整,一些操作会抛出java.lang.UnsupportedOperationException或者可能抛出NPE(例如当Elasticsearch无法解析来自服务的响应时,有时仍然会发生这种情况)
使用新客户端来测试实现,但不要在生产代码中使用它!

为了尝试使用新客户端,需要执行以下步骤:

确保不要配置现有的默认客户端

如果使用 Spring Boot,请从自动配置中排除 Spring Data Elasticsearch

@SpringBootApplication(exclude = ElasticsearchDataAutoConfiguration.class)
public class SpringdataElasticTestApplication {
	// ...
}
 

从应用程序配置中删除与 Spring Data Elasticsearch 相关的属性。如果 Spring Data Elasticsearch 是使用编程配置配置的(请参阅 Elasticsearch 客户端 ),请从 Spring 应用程序上下文中删除这些 beans。

添加依赖

新 Elasticsearch 客户端的依赖项在 Spring Data Elasticsearch 中仍然是可选的,因此需要显式添加它们:

<dependencies>
  <dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>7.17.3</version>
    <exclusions>
      <exclusion>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
    <version>7.17.3</version>
    <exclusions>
      <exclusion>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies> 

使用 Spring Boot 时,需要在 pom.xml 中设置以下属性。

<properties>
  <jakarta-json.version>2.0.1</jakarta-json.version>
</properties> 
新配置类
命令式

为了配置 Spring Data Elasticsearch 以使用新客户端,有必要创建一个派生自 org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration 的配置 bean:

@Configuration
public class NewRestClientConfig extends ElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
			.connectedTo("localhost:9200") //
			.build();
	}
}
 

配置以与旧客户端相同的方式完成,但不再需要创建配置 bean。使用此配置,以下 beans 将在 Spring 应用程序上下文中可用:

  • 一个 RestClient bean,即 Elasticsearch 客户端使用的已配置低级别 RestClient

  • 一个 ElasticsearchClient bean,这是使用 RestClient 的新客户端

  • 一个 ElasticsearchOperations bean,可用于 bean 名称 elasticsearchOperationselasticsearchTemplate,它使用 ElasticsearchClient

响应式风格

要在响应式环境中使用新客户端,唯一的区别是从中派生配置的类:

@Configuration
public class NewRestClientConfig extends ReactiveElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
			.connectedTo("localhost:9200") //
			.build();
	}
}
 

使用此配置,以下 beans 将在 Spring 应用程序上下文中可用:

  • 一个 RestClient bean,即 Elasticsearch 客户端使用的已配置低级别 RestClient

  • 一个 ReactiveElasticsearchClient bean,这是使用 RestClient 的新响应式客户端

  • 一个 ReactiveElasticsearchOperations bean,可用于 bean 名称 reactiveElasticsearchOperationsreactiveElasticsearchTemplate,它使用 ReactiveElasticsearchClient

从 4.4.x 升级到 5.0.x

本节介绍从版本 4.4.x 到 5.0.x 的重大更改以及如何用新引入的功能替换已删除的功能。

弃用

自定义跟踪级别日志记录

通过设置属性 logging.level.org.springframework.data.elasticsearch.client.WIRE=trace 进行日志记录现在已被弃用,Elasticsearch RestClient 提供了一个更好的解决方案,可以通过将 tracer 包的日志记录级别设置为“trace”来激活。

org.springframework.data.elasticsearch.client.erhlc

请参阅 包的变化 ,此包中的所有类均已弃用,因为要使用的默认客户端实现是基于 Elasticsearch 的新 Java 客户端的,请参阅 新的 Elasticsearch 客户端

删除已弃用的代码

DateFormat.noneDateFormat.custom 自版本 4.2 以来已被弃用并已被删除。

自 4.2 以来不推荐使用的 @Document 属性已被删除。对这些使用 @Settings 注解。

@DynamicMapping@DynamicMappingValue 已被删除。请改用 @Document.dynamic@Field.dynamic

重大变更

删除已弃用的调用
建议操作接口中的调用已被删除

SearchOperationsReactiveSearchOperations 都弃用了使用 Elasticsearch 类作为参数的调用。这些现在已被删除,因此这些 API 中对 Elasticsearch 类的依赖性已被清除。

包更改

所有使用或依赖于已弃用的 Elasticsearch RestHighLevelClient 的类都已移至包 org.springframework.data.elasticsearch.client.erhlc 中。通过这一更改,我们现在可以清楚地分离使用旧的已弃用 Elasticsearch 库的代码、使用新的 Elasticsearch 客户端的代码以及独立于客户端实现的代码。此外,到目前为止提供的响应式实现已移至此处,因为此实现包含从 Elasticsearch 库复制和改编的代码。

如果您直接使用 ElasticsearchRestTemplate 而不是 ElasticsearchOperations 接口,您还需要调整导入。

使用 NativeSearchQuery 类时,您需要切换到 NativeQuery 类,它可以从新的 Elasticsearch 客户端库中获取 Query 实例。您会在测试代码中找到大量示例。

转换为 Java 17 记录

以下类已转换为 Record ,您可能需要将 getter 方法的使用从 getProp() 调整为 prop()

  • org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate.IndexResponseMetaData

  • org.springframework.data.elasticsearch.core.ActiveShardCount

  • org.springframework.data.elasticsearch.support.Version

  • org.springframework.data.elasticsearch.support.ScoreDoc

  • org.springframework.data.elasticsearch.core.query.ScriptData

  • org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm

新的 HttpHeaders 类

在 4.4 版之前,客户端配置使用 org.springframework:spring-web 项目中的 HttpHeaders 类。这引入了对该工件的依赖。不使用 spring-web 的用户将面临错误,因为找不到此类。

在 5.0 版中,我们引入了自己的 HttpHeaders 来配置客户端。

因此,如果您在客户端配置中使用header,则需要将 org.springframework.http.HttpHeaders 替换为 org.springframework.data.elasticsearch.support.HttpHeaders

提示:您可以将 org.springframework.http .HttpHeaders 传递给 org.springframework.data.elasticsearch.support.HttpHeadersaddAll() 方法。

新的 Elasticsearch 客户端

Spring Data Elasticsearch 现在使用新的 ElasticsearchClient 并弃用了以前的 RestHighLevelClient

命令式配置

要配置 Spring Data Elasticsearch 以使用新客户端,必须创建一个派生自 org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration 的配置 bean:

@Configuration
public class NewRestClientConfig extends ElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
			.connectedTo("localhost:9200") //
			.build();
	}
}
 

配置以与旧客户端相同的方式完成,但不再需要创建配置 bean。使用此配置,以下 beans 将在 Spring 应用程序上下文中可用:

  • 一个 RestClient bean,即 Elasticsearch 客户端使用的已配置低级别 RestClient

  • 一个 ElasticsearchClient bean,这是使用 RestClient 的新客户端

  • 一个 ElasticsearchOperations bean,可用于 bean 名称 elasticsearchOperationselasticsearchTemplate,它使用 ElasticsearchClient

响应式配置

要在响应式环境中使用新客户端,唯一的区别是从中派生配置的类:

@Configuration
public class NewRestClientConfig extends ReactiveElasticsearchConfiguration {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
			.connectedTo("localhost:9200") //
			.build();
	}
}
 

使用此配置,以下 beans 将在 Spring 应用程序上下文中可用:

  • 一个 RestClient bean,即 Elasticsearch 客户端使用的已配置低级别 RestClient

  • 一个 ReactiveElasticsearchClient bean,这是使用 RestClient 的新响应式客户端

  • 一个 ReactiveElasticsearchOperations bean,可用于 bean 名称 reactiveElasticsearchOperationsreactiveElasticsearchTemplate,它使用 ReactiveElasticsearchClient

还想用老客户端吗?

旧的已弃用的 RestHighLevelClient 仍然可以使用,但您需要将依赖项显式添加到您的应用程序,因为 Spring Data Elasticsearch 不再自动将其拉入:

<!-- include the RHLC, specify version explicitly	-->
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-high-level-client</artifactId>
	<version>7.17.5</version>
	<exclusions>
		<exclusion>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency> 

确保明确指定版本 7.17.6,否则 maven 将解析为 8.5.0,而这不存在。


1。停止维护