前言
Spring Data Elasticsearch 项目将核心 Spring 概念应用于使用 Elasticsearch 搜索引擎的解决方案开发。它提供:
-
Templates 作为存储、搜索、排序文档和构建聚合的高级抽象。
-
Repositories 例如,它使用户能够通过定义具有自定义方法名称的接口来表达查询(有关存储库的基本信息,请参阅 使用 Spring Data Repository )。
您会注意到与 Spring 框架中的 Spring data solr 和 mongodb 支持的相似之处。
1. 新增功能
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.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 中的新功能
-
使用基本身份验证和 SSL 传输的安全 Elasticsearch 集群支持。
-
升级到 Elasticsearch 6.8.1.
-
响应式 Elasticsearch 操作 和 Reactive Elasticsearch Repositories 的响应式编程支持。
-
引入 ElasticsearchEntityMapper 作为 Jackson
ObjectMapper
的替代品。 -
@Field
中的字段名称自定义。 -
支持按查询删除。
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
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 功能。
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
。
我们还提供持久化技术特定的抽象,例如 JpaRepository 或 MongoRepository 。这些接口扩展了 CrudRepository 并公开了底层持久化技术的功能,以及相当通用的持久化技术不可知的接口,例如 CrudRepository 。 |
除了 CrudRepository
之外,还有一个 PagingAndSortingRepository
抽象,它添加了额外的方法来简化对实体的分页访问:
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));
除了查询方法之外,计数和删除查询的查询派生也是可用的。以下列表显示了派生计数查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
以下清单显示了派生删除查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
4.2.查询方法
标准 CRUD 功能存储库通常对底层数据存储有查询。使用 Spring Data,声明这些查询变成一个四步过程:
-
声明一个扩展 Repository 的接口或其子接口之一,并将其键入它应处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … }
-
在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
设置 Spring 以使用 JavaConfig 或使用 XML configuration 为这些接口创建代理实例。
JavaXML@EnableJpaRepositories class Config { … }
本例中使用了 JPA 命名空间。如果您对任何其他存储使用存储库抽象,则需要将其更改为存储模块的适当命名空间声明。换句话说,您应该将
jpa
换成mongodb
等。请注意,JavaConfig 变体不会显式配置包,因为默认使用注解类的包。要自定义要扫描的包,请使用数据存储特定存储库的
@EnableJpaRepositories
注解的basePackage…
属性之一。 -
注入存储库实例并使用它,如以下示例所示:
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
,您可能会发现后者更易于使用。
如果您使用的是响应式存储,您可以选择 ReactiveCrudRepository
或 RxJava3CrudRepository
,具体取决于您使用的响应式框架。
如果您使用的是 Kotlin,您可能会选择 CoroutineCrudRepository
,它利用了 Kotlin 的协程。
另外,如果您需要允许指定 Sort
抽象或在第一种情况下为 Pageable
抽象的方法,则可以扩展 PagingAndSortingRepository
、ReactiveSortingRepository
、RxJava3SortingRepository
或 CoroutineSortingRepository
。请注意,各种排序存储库不再像在 Spring Data 3.0 之前的版本中那样扩展其各自的 CRUD 存储库。因此,如果您需要两者的功能,则需要扩展这两个接口。
如果您不想扩展 Spring Data 接口,您也可以使用 @RepositoryDefinition
注解您的存储库接口。扩展其中一个 CRUD 存储库接口会公开一组完整的方法来操作您的实体。如果您希望对公开的方法有选择性,请将要公开的方法从 CRUD 存储库复制到您的域存储库中。这样做时,您可以更改方法的返回类型。如果可能,Spring Data 将遵循返回类型。例如,对于返回多个实体的方法,您可以选择 Iterable<T>
、List<T>
、Collection<T>
或 VAVR 列表。
如果您的应用程序中的许多存储库应该具有相同的一组方法,您可以定义自己的基接口来继承。这样的接口必须用 @NoRepositoryBean
注解。这可以防止 Spring Data 尝试直接创建它的实例并失败,因为它无法确定该存储库的实体,因为它仍然包含一个通用类型变量。
以下示例显示了如何有选择地公开 CRUD 方法(在本例中为 findById
和 save
):
@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 模块绑定:
-
如果存储库定义 扩展特定于模块的Repository ,则它是特定 Spring Data 模块的有效候选者。
-
如果域类是 用特定于模块的类型注解进行注解 ,那么它是特定 Spring Data 模块的有效候选者。 Spring Data 模块接受第三方注解(例如 JPA 的
@Entity
)或提供自己的注解(例如@Document
用于 Spring Data MongoDB 和 Spring Data Elasticsearch)。
以下示例显示了一个使用模块特定接口(在本例中为 JPA)的存储库:
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository
和 UserRepository
在其类型层次结构中扩展了 JpaRepository
。它们是 Spring Data JPA 模块的有效候选者。
以下示例显示了一个使用通用接口的存储库:
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository
和 AmbiguousUserRepository
在其类型层次结构中仅扩展 Repository
和 CrudRepository
。虽然这在使用唯一的 Spring Data 模块时很好,但是多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。
以下示例显示了一个使用带注解的域类的存储库:
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
注解进行注解。
以下错误示例显示了一个使用带有混合注解的域类的存储库:
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了使用 JPA 和 Spring Data MongoDB 注解的域类。它定义了两个存储库,JpaPersonRepository
和 MongoDBPersonRepository
。一个用于 JPA,另一个用于 MongoDB。 Spring Data 不再能够区分存储库,这会导致未定义的行为。
Repository类型详细信息 和 distinguishing domain class annotations 用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。在同一域类型上使用多个持久化技术特定的注解是可能的,并且可以跨多个持久化技术重用域类型。但是,Spring Data 无法再确定用于绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。基础包定义了扫描存储库接口定义的起点,这意味着存储库定义位于适当的包中。默认情况下,注解驱动配置使用配置类的包。 基于 XML 的配置中的基础包 是强制性的。
以下示例显示了基础包的注解驱动配置:
@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
(默认值)结合了CREATE
和USE_DECLARED_QUERY
。它首先查找已声明的查询,如果未找到已声明的查询,它会创建一个基于自定义方法名称的查询。这是默认的查找策略,因此,如果您没有明确配置任何内容,就会使用它。它允许通过方法名称快速定义查询,还可以根据需要通过引入声明的查询来自定义调整这些查询。
4.4.2.查询创建
Spring Data 存储库基础结构中内置的查询构建器机制对于构建存储库实体的约束查询很有用。
以下示例显示了如何创建多个查询:
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
充当分隔符以指示实际条件谓词的开始。在非常基本的层面上,您可以定义实体属性的条件并将它们与 And
和 Or
连接起来。
解析该方法的实际结果取决于您为其创建查询的持久化存储。但是,有一些一般事项需要注意:
-
这些表达式通常是属性遍历与可以连接的运算符相结合。您可以将属性表达式与
AND
和OR
结合使用。您还可以获得对属性表达式的Between
、LessThan
、GreaterThan
和Like
等运算符的支持。支持的运算符可能因数据存储而异,因此请查阅参考文档的相应部分。 -
方法解析器支持为单个属性(例如
findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性(通常是String
实例——例如findByLastnameAndFirstnameAllIgnoreCase(…)
)设置一个IgnoreCase
标志。是否支持忽略大小写可能因存储而异,具体存储的查询方式请查阅参考文档中的相关章节。 -
您可以通过将
OrderBy
子句附加到引用属性的查询方法并提供排序方向(Asc
或Desc
)来应用静态排序。要创建支持动态排序的查询方法,请参阅“分页、循环大数据量、排序”。
4.4.3.属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。在创建查询时,您已经确保解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设 Person
有一个 Address
和一个 ZipCode
。在这种情况下,该方法会创建 x.address.zipCode
属性遍历。解析算法首先将整个部分 (AddressZipCode
) 解释为属性,并检查域类中是否有具有该名称(未大写)的属性。如果算法成功,它将使用该属性。如果不是,该算法会将源代码从右侧的驼峰式部分拆分为头部和尾部,并尝试找到相应的属性——在我们的示例中为 AddressZip
和 Code
。如果该算法找到具有该头部的属性,它会获取尾部并继续从那里向下构建树,以刚才描述的方式拆分尾部。如果第一次分割不匹配,算法将分割点向左移动(Address
,ZipCode
)并继续。
虽然这适用于大多数情况,但算法可能会选择错误的属性。假设 Person
类也有一个 addressZip
属性。该算法将在第一轮拆分中匹配,选择错误的属性,然后失败(因为 addressZip
的类型可能没有 code
属性)。
要解决这种歧义,您可以在方法名称中使用 _
来手动定义遍历点。所以我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰命名法)。
4.4.4.分页、迭代大结果、排序
要处理查询中的参数,请按照前面示例中所见定义方法参数。除此之外,基础schema还可以识别某些特定类型,如 Pageable
和 Sort
,以动态地对您的查询应用分页和排序。以下示例演示了这些功能:
Pageable
、Slice
和 Sort
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
采用 Sort 和 Pageable 的 API 期望将非 null 值传递给方法。如果您不想应用任何排序或分页,请使用 Sort.unsorted() 和 Pageable.unpaged() 。 |
第一种方法允许您将 org.springframework.data.domain.Pageable
实例传递给查询方法,以将分页动态添加到静态定义的查询中。 Page
知道可用元素和页面的总数。它通过基础schema触发计数查询来计算总数来实现。由于这可能很昂贵(取决于所使用的存储),您可以改为返回 Slice
。 Slice
只知道下一个 Slice
是否可用,这在遍历更大的结果集时可能就足够了。
排序选项也通过 Pageable
实例处理。如果您只需要排序,请将 org.springframework.data.domain.Sort
参数添加到您的方法中。如您所见,返回 List
也是可能的。在这种情况下,不会创建构建实际 Page
实例所需的额外元数据(这反过来意味着不会发出本来需要的额外计数查询)。相反,它将查询限制为仅查找给定范围的实体。
要了解您为整个查询获得了多少页,您必须触发额外的计数查询。默认情况下,此查询派生自您实际触发的查询。 |
哪种方法合适?
下表中概述的可能的查询方法返回类型可能最好地显示了 Spring Data 抽象提供的值。该表显示了您可以从查询方法返回哪些类型
方法 | 获取的数据量 | 查询结构 | 约束条件 |
---|---|---|---|
所有结果。 |
单一查询。 |
查询结果可能会耗尽所有内存。获取所有数据可能非常耗时。 |
|
所有结果。 |
单一查询。 |
查询结果可能会耗尽所有内存。获取所有数据可能非常耗时。 |
|
根据 |
通常使用游标的单个查询。 |
流必须在使用后关闭以避免资源泄漏。 |
|
|
根据 |
通常使用游标的单个查询。 |
Store 模块必须提供响应式基础设施。 |
|
根据 |
通常使用游标的单个查询。 |
|
|
|
一对多查询从 |
一个
|
|
|
从 |
很多时候,
|
分页和排序
您可以使用属性名称定义简单的排序表达式。您可以连接表达式以将多个条件收集到一个表达式中。
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
要以一种类型更安全的方式来定义排序表达式,请从要为其定义排序表达式的类型开始,然后使用方法引用来定义要排序的属性。
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,您还可以使用生成的元模型类型来定义排序表达式:
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
4.4.5.限制查询结果
您可以使用可以互换使用的 first
或 top
关键字来限制查询方法的结果。您可以将可选数值附加到 top
或 first
以指定要返回的最大结果大小。如果省略数字,则假定结果大小为 1.以下示例显示了如何限制查询大小:
Top
和 First
限制查询的结果大小User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
对于支持不同查询的数据存储,限制表达式还支持 Distinct
关键字。此外,对于将结果集限制为一个实例的查询,支持使用Optional
关键字将结果包装成。
如果分页或切片应用于限制查询分页(以及可用页数的计算),则在有限结果内应用。
通过使用 Sort 参数限制结果与动态排序相结合,您可以表达“K”最小元素和“K”最大元素的查询方法。 |
4.4.6.存储库方法返回集合或可迭代对象
返回多个结果的查询方法可以使用标准 Java Iterable
、List
和 Set
。除此之外,我们支持返回 Spring Data 的 Streamable
、Iterable
的自定义扩展,以及 Vavr 提供的集合类型。请参阅解释所有可能的 查询方法返回类型 的附录。
使用 Streamable 作为查询方法返回类型
您可以使用 Streamable
作为 Iterable
或任何集合类型的替代。它提供了方便的方法来访问非并行 Stream
(从 Iterable
中丢失)以及直接 ….filter(…)
和 ….map(…)
元素并将 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 允许您将这些包装器类型用作查询方法返回类型,前提是它们满足以下条件:
-
该类型实现了
Streamable
。 -
该类型公开了一个构造函数或一个名为
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 源类型 |
---|---|---|
|
|
|
|
|
|
|
|
|
您可以将第一列中的类型(或其子类型)用作查询方法返回类型,并将第二列中的类型用作实现类型,具体取决于实际查询结果(第三列)的 Java 类型。或者,您可以声明 Traversable
(相当于 Vavr Iterable
),然后我们从实际返回值派生实现类。也就是说,java.util.List
变成 Vavr List
或 Seq
,java.util.Set
变成 Vavr LinkedHashSet
Set
,等等。
4.4.7.流式查询结果
您可以使用 Java 8 Stream<T>
作为返回类型来逐步处理查询方法的结果。不是将查询结果包装在 Stream
中,而是使用特定于数据存储的方法来执行流式处理,如以下示例所示:
Stream<T>
流式传输查询结果@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
Stream 可能包含底层数据存储特定的资源,因此必须在使用后关闭。您可以使用 close() 方法或使用 Java 7 try-with-resources 块手动关闭 Stream ,如以下示例所示: |
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 元注解让工具供应商(例如 IDEA 、 Eclipse 和 Kotlin )以通用方式提供空安全支持,而无需对 Spring 注解进行硬编码支持。要为查询方法启用可空性约束的运行时检查,您需要使用 package-info.java
中的 Spring @NonNullApi
在包级别激活不可空性,如以下示例所示:
package-info.java
中声明不可空性@org.springframework.lang.NonNullApi
package com.acme;
一旦非空默认设置到位,存储库查询方法调用将在运行时针对可空性约束进行验证。如果查询结果违反定义的约束,则抛出异常。当该方法将返回 null
但被声明为不可为空(默认情况下,在存储库所在的包上定义了注解)时会发生这种情况。如果您想再次选择加入可为空的结果,请在各个方法上有选择地使用 @Nullable
。使用本节开头提到的结果包装器类型继续按预期工作:空结果被转换为表示不存在的值。
以下示例显示了刚才描述的许多技术:
package com.acme; (1)
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 。当传递给方法的 emailAddress 是 null 时抛出 IllegalArgumentException 。 |
3 | 当查询没有产生结果时返回 null 。还接受 null 作为 emailAddress 的值。 |
4 | 当查询没有产生结果时返回 Optional.empty() 。当传递给方法的 emailAddress 是 null 时抛出 IllegalArgumentException 。 |
基于 Kotlin 的Repositoriey中的可空性
Kotlin 将 可空性约束 的定义嵌入到语言中。 Kotlin 代码编译为字节码,字节码不通过方法签名表达可空性约束,而是通过编译元数据表达。确保在您的项目中包含 kotlin-reflect
JAR 以启用对 Kotlin 的可空性约束的自省。 Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
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 存储库的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注解,您可以根据实际使用的存储模块更改它。这同样适用于 EntityManagerFactory bean 的定义。请参阅涵盖存储特定配置的部分。 |
4.5.2. XML配置
每个 Spring Data 模块都包含一个 repositories
元素,它允许您定义 Spring 为您扫描的基础包,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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 的实例化中排除,您可以使用以下配置:
@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
,您可以使用它,如下所示:
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
4.6. Spring Data Repository的自定义实现
Spring Data 提供了多种选项来创建几乎没有编码的查询方法。但是当这些选项不符合您的需要时,您还可以为存储库方法提供您自己的自定义实现。本节描述如何做到这一点。
4.6.1.自定义单个Repository
要使用自定义功能丰富存储库,您必须首先定义片段接口和自定义功能的实现,如下所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
与片段接口对应的类名中最重要的部分是Impl 后缀。 |
实现本身不依赖于 Spring Data,可以是一个普通的 Spring bean。因此,您可以使用标准依赖项注入行为来注入对其他 bean(例如 JdbcTemplate
)的引用,参与方面等。
然后你可以让你的存储库接口扩展片段接口,如下所示:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
使用您的存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。
Spring Data 存储库是通过使用构成存储库组合的片段来实现的。片段是基础存储库、功能方面(例如 QueryDsl )和自定义接口及其实现。每次向存储库接口添加一个接口时,您都会通过添加一个片段来增强组合。基本存储库和存储库方面实现由每个 Spring Data 模块提供。
以下示例显示了自定义接口及其实现:
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
的自定义存储库的接口:
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
存储库可能由多个自定义实现组成,这些实现按声明的顺序导入。自定义实现比基本实现和存储库方面具有更高的优先级。如果两个片段提供相同的方法签名,此排序可让您覆盖基本存储库和方面方法并解决歧义。存储库片段不限于在单个Repository接口中使用。多个存储库可以使用片段接口,让您可以跨不同的存储库重用自定义。
以下示例显示了存储库片段及其实现:
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
}
}
以下示例显示了一个使用上述存储库片段的存储库:
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
存储库基础结构尝试通过扫描它在其中找到存储库的包下的类来自动检测自定义实现片段。这些类需要遵循附加后缀默认为 Impl
的命名约定。
以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
前面示例中的第一个配置尝试查找名为 com.acme.repository.CustomizedUserRepositoryImpl
的类以充当自定义存储库实现。第二个示例尝试查找 com.acme.repository.CustomizedUserRepositoryMyPostfix
。
歧义的解决
如果在不同的包中找到具有匹配类名的多个实现,Spring Data 会使用 bean 名称来标识要使用的那个。
给定前面显示的 CustomizedUserRepository
的以下两个自定义实现,使用第一个实现。它的 bean 名称是 customizedUserRepositoryImpl
,与片段接口 (CustomizedUserRepository
) 的名称加上后缀 Impl
相匹配。
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 定义,而不是自己创建一个。以下示例显示了如何手动连接自定义实现:
class MyClass {
MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
…
}
}
4.6.2.自定义基本存储库
上一节 中描述的方法需要在您想要自定义基本存储库行为时自定义每个存储库接口,以便影响所有存储库。要改为更改所有存储库的行为,您可以创建一个扩展特定于持久化技术的存储库基类的实现。然后,此类充当存储库代理的自定义基类,如以下示例所示:
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
来执行此操作,如以下示例所示:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
4.7.从聚合根发布事件
存储库管理的实体是聚合根。在领域驱动设计应用程序中,这些聚合根通常会发布领域事件。 Spring Data 提供了一个名为 @DomainEvents
的注解,您可以在聚合根的方法上使用它来使该发布尽可能简单,如以下示例所示:
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 的集成,如以下示例所示:
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
,如以下示例所示:
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
注解来启用的,如以下示例所示:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
@EnableSpringDataWebSupport
注解注册了一些组件。我们将在本节后面讨论这些内容。它还在类路径上检测 Spring HATEOAS 并为其注册集成组件(如果存在)。
基本Web 支持
上一节 中显示的配置注册了一些基本组件:
-
使用
DomainClassConverter
类 让 Spring MVC 从请求参数或路径变量中解析存储库管理域类的实例。 -
HandlerMethodArgumentResolver
实现让 Spring MVC 从请求参数中解析Pageable
和Sort
实例。 -
Jackson模块 反序列化
Point
和Distance
等类型,或存储特定类型,具体取决于所使用的 Spring Data模块。
使用 DomainClassConverter
类
DomainClassConverter
类允许您直接在 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
的实例。注册使 Pageable
和 Sort
成为有效的控制器方法参数,如以下示例所示:
@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
实例:
|
您要检索的页面。 0 索引,默认为 0. |
|
您要检索的页面的大小。默认为 20. |
|
应按 |
要自定义此行为,请分别注册一个实现 PageableHandlerMethodArgumentResolverCustomizer
接口或 SortHandlerMethodArgumentResolverCustomizer
接口的 bean。它的 customize()
方法被调用,让您更改设置,如以下示例所示:
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果设置现有 MethodArgumentResolver
的属性不足以满足您的目的,请扩展 SpringDataWebConfiguration
或启用 HATEOAS 的等效项,覆盖 pageableResolver()
或 sortResolver()
方法,并导入您的自定义配置文件而不是使用 @Enable
注解。
如果您需要从请求中解析多个 Pageable
或 Sort
实例(例如,对于多个表),您可以使用 Spring 的 @Qualifier
注解来区分它们。然后请求参数必须以 ${qualifier}_
为前缀。以下示例显示了生成的方法签名:
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必须填充 thing1_page
、 thing2_page
等等。
传入方法的默认 Pageable
等同于 PageRequest.of(0, 20)
,但您可以使用 Pageable
参数上的 @PageableDefault
注解对其进行自定义。
Page
和 Slice
超媒体支持
Spring HATEOAS 附带一个表示模型类 (PagedModel
/SlicedModel
),它允许使用必要的 Page
/Slice
元数据以及链接来丰富 Page
或 Slice
实例的内容,让客户端轻松导航页面。 Page
到 PagedModel
的转换是通过 Spring HATEOAS RepresentationModelAssembler
接口的实现完成的,称为 PagedResourcesAssembler
。同样,可以使用 SlicedResourcesAssembler
将 Slice
实例转换为 SlicedModel
。以下示例显示了如何使用 PagedResourcesAssembler
作为控制器方法参数,因为 SlicedResourcesAssembler
的工作方式完全相同:
@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
可能会附加prev
和next
链接,具体取决于页面的状态。这些链接指向方法映射到的 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.Distance
和 org.springframework.data.geo.Point
。
一旦 Web 支持 启用并且 com.fasterxml.jackson.databind.ObjectMapper
可用,这些模块就会被导入。
在初始化期间 SpringDataJacksonModules
和 SpringDataJacksonConfiguration
一样,被基础设施接收,这样声明的 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
个别模块可能会提供额外的 |
Web 数据绑定支持
您可以使用 Spring Data 投影(在 [projections] 中描述)通过使用 JSONPath 表达式(需要 Jayway JsonPath )或 XPath 表达式(需要 XmlBeam )来绑定传入的请求有效负载,如以下示例所示:
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以将前面示例中显示的类型用作 Spring MVC 处理程序方法参数,或者通过在 RestTemplate
的方法之一上使用 ParameterizedTypeReference
。前面的方法声明将尝试在给定文档中的任何位置查找 firstname
。 lastname
XML 查找在传入文档的顶层执行。它的 JSON 变体首先尝试顶级 lastname
,但如果前者未返回值,也会尝试嵌套在 user
子文档中的 lastname
。这样,无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点),就可以轻松缓解源文档结构的变化。
如 [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
运行它。
类型信息通常从方法的返回类型中解析出来。由于该信息不一定与域类型匹配,因此使用 QuerydslPredicate 的 root 属性可能是个好主意。 |
以下示例显示如何在方法签名中使用 @QuerydslPredicate
:
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
1 | 解析查询字符串参数以匹配 Predicate for User 。 |
默认绑定如下:
-
Object
与eq
一样的简单属性。 -
Object
在像contains
这样的集合上。 -
Collection
与in
一样的简单属性。
您可以通过 @QuerydslPredicate
的 bindings
属性或使用 Java 8 default methods
并将 QuerydslBinderCustomizer
方法添加到存储库接口来自定义这些绑定,如下所示:
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QuerydslPredicateExecutor 提供对 Predicate 特定查找器方法的访问。 |
2 | QuerydslBinderCustomizer 定义在存储库接口上自动拾取和快捷方式 @QuerydslPredicate(bindings=…) 。 |
3 | 将 username 属性的绑定定义为简单的 contains 绑定。 |
4 | 将 String 属性的默认绑定定义为不区分大小写的 contains 匹配。 |
5 | 从 Predicate 分辨率中排除 password 属性。 |
在从存储库或 @QuerydslPredicate 应用特定绑定之前,您可以注册一个持有默认 Querydsl 绑定的 QuerydslBinderCustomizerDefaults bean。 |
4.8.3.Repository 填充器
如果您使用 Spring JDBC 模块,您可能熟悉使用 SQL 脚本填充 DataSource
的支持。类似的抽象在存储库级别可用,尽管它不使用 SQL 作为数据定义语言,因为它必须与存储无关。因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。
假设您有一个名为 data.json
的文件,其中包含以下内容:
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的存储库命名空间的填充器元素来填充存储库。要将前面的数据填充到您的 PersonRepository
,请声明一个类似于以下内容的填充器:
<?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 解组存储库填充器:
<?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:
@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 客户端
使用响应式堆时,配置必须从不同的类派生:
@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,在某些情况下 |
@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 客户端库提供的功能的 |
@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和其他参数设置选项。
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 RestClient
的 org.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 文档以获取完整的属性列表):-
indexName
:存储此实体的索引的名称。这可以包含一个 SpEL 模板表达式,如"log-#{T(java.time.LocalDate).now().toString()}"
-
createIndex
:标记是否在存储库引导时创建索引。默认值为 true 。见Automatic creation of indices with the corresponding mapping
-
-
@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 。 -
analyzer
、searchAnalyzer
、normalizer
用于指定自定义分析器和规范器。
-
-
@GeoPoint
:将字段标记为 geo_point 数据类型。如果该字段是GeoPoint
类的实例,则可以省略。 -
@ValueConverter
定义了一个用于转换给定属性的类。与注册的 SpringConverter
不同的是,它只转换带注解的属性,而不是给定类型的每个属性。
映射元数据基础设施在一个独立的 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_time 和 epoch_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>
类型支持的类是 Integer
、Long
、Float
、Double
、Date
和实现 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";
}
6.1.2.映射规则
类型提示
映射使用 type hints 嵌入到发送到服务的文档中以允许泛型类型映射。这些类型提示在文档中表示为 _class
属性,并为每个聚合根编写。
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 ),以便在首次从存储中读取数据时实体信息可用。 |
@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 正确检索文档。 |
地理空间类型
Point
和 GeoPoint
等地理空间类型被转换为 lat/lon 对。
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 文档)
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 hints 和 Custom Conversions 时应用与聚合根相同的映射规则。
public class Person {
// ...
List<Person> friends;
}
{
// ...
"friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
}
map
对于 Maps 内的值,在涉及 type hints 和 Custom Conversions 时应用与聚合根相同的映射规则。然而,映射键需要一个字符串才能被 Elasticsearch 处理。
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
允许为映射域和简单类型注册特定规则。
@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 | 设置用于从搜索结果中读取 DomainType 的 Converter 。 |
7. Elasticsearch操作
Spring Data Elasticsearch 使用多个接口来定义可以针对 Elasticsearch 索引调用的操作(有关响应式接口的描述,请参阅 响应式 Elasticsearch 操作)。
-
IndexOperations
定义索引级别的操作,如创建或删除索引。 -
DocumentOperations
定义了根据实体的 id 存储、更新和检索实体的操作。 -
SearchOperations
定义使用查询搜索多个实体的操作 -
ElasticsearchOperations
结合了DocumentOperations
和SearchOperations
接口。
这些接口对应于 ElasticsearchAPI 的结构。
接口的默认实现提供:
-
索引管理功能。
-
域类型的读/写映射支持。
-
丰富的查询和条件 api。
-
资源管理和异常转换。
索引管理和自动创建索引和映射。
None of these operations are done automatically 通过 使用 Spring Data Elasticsearch 存储库时支持自动创建索引和编写映射,请参阅 使用相应的映射自动创建索引 |
7.1.使用示例
该示例展示了如何在 Spring REST 控制器中使用注入的 ElasticsearchOperations
实例。该示例假定 Person
是一个用 @Document
、 @Id
等注解的类(参见 映射注解概述 )。
@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 集群执行高级命令的网关。
ReactiveElasticsearchTemplate
是 ReactiveElasticsearchOperations
的默认实现。
7.2.1.响应式性 Elasticsearch 操作
要开始,ReactiveElasticsearchOperations
需要了解要使用的实际客户端。请参阅 响应式Rest 客户端 了解有关客户端的详细信息以及如何配置它。
响应式性操作用法
ReactiveElasticsearchOperations
让您可以保存、查找和删除领域对象,并将这些对象映射到存储在 Elasticsearch 中的文档。
考虑以下:
@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 索引中查找具有匹配 id 的 Person 。 |
3 | 在 marvel 索引中删除从给定实例中提取的具有匹配 id 的 Person 。 |
4 | 统计 marvel 索引中的文档总数。 |
5 | 别忘了 subscribe() 。 |
7.3.搜索结果类型
当使用 DocumentOperations
接口的方法检索文档时,将只返回找到的实体。当使用 SearchOperations
接口的方法进行搜索时,每个实体的附加信息都可用,例如找到的实体的 score 或 sortValues。
为了返回此信息,每个实体都包装在一个 SearchHit
对象中,该对象包含此实体特定的附加信息。这些 SearchHit
对象本身在 SearchHits
对象中返回,该对象还包含有关整个搜索的信息,如 maxScore 或请求的聚合。现在可以使用以下类和接口:
包含以下信息:
-
Id
-
得分
-
排序值
-
高亮字段
-
内部命中(这是一个嵌入的
SearchHits
对象,包含最终返回的内部命中) -
检索到的 <T> 类型的实体
包含以下信息:
-
总命中数
-
总命中关系
-
最高分
-
SearchHit<T>
对象列表 -
返回的聚合
-
返回建议结果
定义一个 Spring Data Page
,其中包含一个 SearchHits<T>
元素,可用于使用存储库方法进行分页访问。
由 ElasticsearchRestTemplate
中的低级滚动 API 函数返回,它使用 Elasticsearch 滚动 ID 丰富了 SearchHits<T>
。
SearchOperations
接口的流函数返回的迭代器。
ReactiveSearchOperations
有返回 Mono<ReactiveSearchHits<T>>
的方法,它包含与 SearchHits<T>
对象相同的信息,但会将包含的 SearchHit<T>
对象作为 Flux<SearchHit<T>>
而不是列表提供。
7.4.查询
SearchOperations
和 ReactiveSearchOperations
接口中定义的几乎所有方法都有一个 Query
参数,该参数定义要执行的搜索查询。 Query
是一个接口,Spring Data Elasticsearch 提供了三种实现:CriteriaQuery
、StringQuery
和 NativeQuery
。
7.4.1.条件查询
基于 CriteriaQuery
的查询允许创建查询来搜索数据,而无需了解 Elasticsearch 查询的语法或基础知识。它们允许用户通过简单地链接和组合 Criteria
对象来构建查询,这些对象指定了搜索文档必须满足的条件。
当谈论 AND 或 OR 组合条件时请记住,在 Elasticsearch 中,AND 被转换为 must 条件,OR 被转换为 should |
Criteria
及其用法最好通过示例来解释(假设我们有一个带有 price
属性的 Book
实体):
Criteria criteria = new Criteria("price").is(42.0);
Query query = new CriteriaQuery(criteria);
同一字段的条件可以链接,它们将与逻辑 AND 组合:
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0);
Query query = new CriteriaQuery(criteria);
链接 Criteria
时,默认使用 AND 逻辑:
Criteria criteria = new Criteria("lastname").is("Miller") (1)
.and("firstname").is("James") (2)
Query query = new CriteriaQuery(criteria);
1 | 第一个Criteria |
2 | and() 创建一个新的 Criteria 并将其链接到第一个。 |
如果要创建嵌套查询,则需要为此使用子查询。假设我们要查找所有姓氏为 Miller 且名字为 Jack 或 John 的人:
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 存储库实现的详细信息。
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 查询方法转换成的简短示例:
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 支持的关键字列表如下所示。
关键词 | 示例 | Elasticsearch 查询字符串 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
不支持使用 GeoJson 参数构建 Geo-shape 查询的方法名称。如果您需要在存储库中具有这样的功能,请在自定义存储库实现中使用 ElasticsearchOperations 和 CriteriaQuery 。 |
8.2.3.方法返回类型
可以将存储库方法定义为具有以下用于返回多个元素的返回类型:
-
List<T>
-
Stream<T>
-
SearchHits<T>
-
List<SearchHit<T>>
-
Stream<SearchHit<T>>
-
SearchPage<T>
8.2.4.使用@Query 注解
@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"
}
}
}
}
@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 中的域对象,只需为其创建一个接口。在您真正继续进行之前,您需要一个实体。
Person
实体public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
请注意 id 属性需要是 String 类型。 |
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 配置:
@Configuration
@EnableReactiveElasticsearchRepositories
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
return ReactiveRestClients.create(ClientConfiguration.localhost());
}
}
因为前面示例中的存储库扩展了 ReactiveSortingRepository
,所以所有 CRUD 操作以及对实体进行排序访问的方法都可用。使用存储库实例是将其注入客户端的依赖性问题,如以下示例所示:
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 存储库支持。
@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 存储库。
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实例 中所述。
<?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 Client
或 Rest Client
元素在上下文中注册 Elasticsearch Server
的实例。
<?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>
<?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 配置启用审计以注册所需的基础结构组件。有关配置示例,请参阅存储特定部分。
不需要仅跟踪创建和修改日期的应用程序会使其实体实现 |
9.1.1.基于注解的审计元数据
我们提供 @CreatedBy
和 @LastModifiedBy
来捕获创建或修改实体的用户,以及 @CreatedDate
和 @LastModifiedDate
来捕获更改发生的时间。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
// … further properties omitted
}
如您所见,可以根据要捕获的信息有选择地应用注解。指示在进行更改时捕获的注解可用于 JDK8 日期和时间类型 long
、 Long
以及旧版 Java Date
和 Calendar
类型的属性。
审计元数据不一定需要存在于根级实体中,但可以添加到嵌入式实体中(取决于实际使用的存储),如下面的代码片段所示。
class Customer {
private AuditMetadata auditingMetadata;
// … further properties omitted
}
class AuditMetadata {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
}
9.1.3. AuditorAware
如果您使用 @CreatedBy
或 @LastModifiedBy
,审计基础设施需要以某种方式了解当前主体。为此,我们提供了一个 AuditorAware<T>
SPI 接口,您必须实现该接口以告知基础schema当前与应用程序交互的用户或系统是谁。通用类型 T
定义了用 @CreatedBy
或 @LastModifiedBy
注解的属性必须是什么类型。
以下示例显示了使用 Spring Security 的 Authentication
对象的接口的实现:
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
对象的接口的实现:
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;
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。这是应用实体修改的推荐方式。现有存储特定 |
10.1.实现实体回调
EntityCallback
通过其通用类型参数直接与其域类型相关联。每个 Spring Data 模块通常附带一组预定义的 EntityCallback
接口,涵盖实体生命周期。
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。 |
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() 的调用站点推断出来。 |
实现适合您的应用程序需求的接口,如下例所示:
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
以下示例解释了一组有效的实体回调注册:
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 表达式实现的回调不会公开类型信息,因此使用不可分配的实体调用这些回调会影响回调吞吐量。使用 class 或 enum 为回调 bean 启用类型过滤。 |
4 | 在单个实现类中组合多个实体回调接口。 |
10.3. Elasticsearch 实体回调
Spring Data Elasticsearch 在内部使用 EntityCallback
API 来支持审计,并对以下回调做出响应式:
打回来 | 方法 | 描述 | 命令 |
---|---|---|---|
响应式/BeforeConvertCallback |
|
在域对象转换为 |
|
响应式/AfterLoadCallback |
|
在将 Elasticsearch 的结果读入 |
|
响应式/AfterConvertCallback |
|
在从 Elasticsearch 读取结果数据时从 |
|
响应式/审计实体回调 |
|
标记一个可审计的实体created或modified |
100 |
响应式/AfterSaveCallback |
|
在保存域对象后调用。 |
|
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 属性用于将关系的名称(question、answer、comment 或 vote)与父 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
属性作为路由的值。
这对于父子关系只有一个级别的所有用例都是正确的。如果它更深,比如子-父母-祖父母关系 - 如上例中的 vote → answer → question - 然后需要使用下一节中描述的技术明确指定路由(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 中存储实体时定义了路由,则在执行 get 或 delete 操作时必须提供相同的值。对于不使用实体的方法 - 如 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 | sortModes 、sortOrders 和 sortMissingValues 是可选的,但如果设置了它们,条目数必须与 sortFields 元素数匹配 |
13.2.索引映射
当 Spring Data Elasticsearch 使用 IndexOperations.createMapping()
方法创建索引映射时,它使用 映射注解概述 中描述的注解,尤其是 @Field
注解。除此之外,还可以将 @Mapping
注解添加到类中。此注解具有以下属性:
-
mappingPath
JSON 格式的类路径资源;如果这不为空,则将其用作映射,不进行其他映射处理。 -
enabled
当设置为 false 时,此标志将写入映射并且不会进行进一步处理。 -
dateDetection
和numericDetection
在未设置为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
类,可用于获得按地理距离排序的搜索操作的结果。
如果要检索的类具有名为 location 的 GeoPoint
属性,则以下 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 文件,例如:
{
"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
。搜索模板脚本可以这样保存:
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 />
元素的属性:
名称 | 描述 |
---|---|
|
定义要在自动检测模式下为扩展 |
|
定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选类。默认为 |
|
确定用于创建查找器查询的策略。有关详细信息,请参见“查询查找策略”。默认为 |
|
定义用于搜索包含外部定义查询的属性文件的位置。 |
|
是否应考虑嵌套的存储库接口定义。默认为 |
附录 B:Populators 命名空间参考
<populator /> 元素
<populator />
元素允许通过 Spring Data 存储库基础结构填充数据存储。[2]
名称 | 描述 |
---|---|
|
在哪里可以找到从存储库中读取对象的文件。 |
附录 C:存储库查询关键字
支持的查询方式主题关键词
下表列出了Spring Data Repository查询推导机制普遍支持的主语关键字来表达谓语。请查阅特定于存储的文档以获取受支持关键字的确切列表,因为特定存储可能不支持此处列出的某些关键字。
关键词 | 描述 |
---|---|
|
一般查询方法通常返回存储库类型、 |
|
存在投影,通常返回 |
|
返回数字结果的计数投影。 |
|
删除查询方法不返回任何结果 ( |
|
将查询结果限制为前 |
|
使用不同的查询只返回唯一的结果。请查阅特定于存储的文档是否支持该功能。此关键字可以出现在主题的任何位置,介于 |
支持的查询方法谓词关键字和修饰符
下表列出了 Spring Data 存储库查询派生机制普遍支持的谓词关键字。但是,请查阅特定于存储的文档以获取受支持关键字的确切列表,因为特定存储可能不支持此处列出的某些关键字。
逻辑关键字 | 关键字表达式 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
除了过滤谓词之外,还支持以下修饰符列表:
关键词 | 描述 |
---|---|
|
与谓词关键字一起使用以进行不区分大小写的比较。 |
|
忽略所有合适属性的大小写。在查询方法谓词的某处使用。 |
|
指定静态排序顺序,后跟属性路径和方向(例如 |
附录 D:存储库查询返回类型
支持的查询返回类型
下表列出了 Spring Data 存储库通常支持的返回类型。但是,请查阅特定于存储的文档以获取支持的返回类型的确切列表,因为特定存储可能不支持此处列出的某些类型。
地理空间类型(例如 GeoResult 、 GeoResults 和 GeoPage )仅适用于支持地理空间查询的数据存储。一些存储模块可能会定义自己的结果包装器类型。 |
返回类型 | 描述 |
---|---|
|
表示没有返回值。 |
Primitives |
Java原语。 |
Wrapper types |
Java 包装器类型。 |
|
一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回 |
|
一个 |
|
一个 |
|
一个 |
|
Java 8 或番石榴 |
|
Scala 或 Vavr |
|
Java 8 |
|
|
实现 |
公开构造函数或 |
Vavr |
Vavr 集合类型。有关详细信息,请参阅 支持 Vavr 集合。 |
|
一个 |
|
Java 8 |
|
一定大小的数据块,指示是否有更多可用数据。需要一个 |
|
带有附加信息的 |
|
带有附加信息的结果条目,例如到参考位置的距离。 |
|
|
|
|
|
Project Reactor |
|
Project Reactor |
|
一个 RxJava |
|
一个 RxJava |
|
一个 RxJava |
附录 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 中,不同的查询类(如 IndexQuery
或 SearchQuery
)具有采用索引名称或它们正在操作的索引名称的属性。如果未设置这些,则检查传入的实体以检索在 @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
现在扩展了 DocumentOperations
和 SearchOperations
并且有方法可以访问 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 显示了它们应该被替换的内容。
/*
* 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);
删除
-
如前所述,
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 响应中返回信息,因此无需公开此低级功能。 -
低级方法
startScroll
、continueScroll
和clearScroll
已从ElasticsearchOperations
接口中删除。对于低级滚动 API 访问,ElasticsearchRestTemplate
类现在有searchScrollStart
、searchScrollContinue
和searchScrollClear
方法。
从 4.0.x 升级到 4.1.x
本节介绍从版本 4.0.x 到 4.1.x 的重大更改,以及如何用新引入的功能替换已删除的功能。
弃用
可以通过将 en 实体的属性命名为 id
或 document
来将其定义为 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 中已被弃用,并且不再使用它们的值。
从 4.1.x 升级到 4.2.x
本节介绍从版本 4.1.x 到 4.2.x 的重大更改以及如何用新引入的功能替换已删除的功能。
移除
用于在实体中设置得分返回值的 @Score
注解在 4.0 版中已弃用并已被删除。得分值在封装返回实体的 SearchHit
实例中返回。
org.springframework.data.elasticsearch.ElasticsearchException
类已被删除。其余用法已替换为 org.springframework.data.mapping.MappingException
和 org.springframework.dao.InvalidDataAccessApiUsageException
。
已弃用的 ScoredPage
、ScrolledPage
、@AggregatedPage
和实现已被删除。
已弃用的 GetQuery
和 DeleteQuery
已被删除。
ReactiveSearchOperations
和 ReactiveDocumentOperations
中已弃用的 find
方法已被删除。
重大变更
刷新策略
枚举包已更改
在 4.1 中可以通过覆盖自定义配置类中的方法 AbstractReactiveElasticsearchConfiguration.refreshPolicy()
来配置 ReactiveElasticsearchTemplate
的刷新策略。此方法的返回值是类 org.elasticsearch.action.support.WriteRequest.RefreshPolicy
的一个实例。
现在配置必须返回 org.springframework.data.elasticsearch.core.RefreshPolicy
。此枚举具有与以前相同的值并触发相同的行为,因此只需调整 import
语句。
刷新行为
ElasticsearchOperations
和 ReactiveElasticsearchOperations
现在显式使用模板上的 RefreshPolicy
集用于写入请求(如果不为空)。如果刷新策略为 null,则不会执行任何特殊操作,因此使用集群默认值。 ElasticsearchOperations
在此版本之前始终使用集群默认值。
为 ElasticsearchRepository
和 ReactiveElasticsearchRepository
提供的实现将在刷新策略为空时进行显式刷新。这与以前版本中的行为相同。如果设置了刷新策略,那么它也将被存储库使用。
刷新配置
当使用 ElasticsearchConfigurationSupport
、 AbstractElasticsearchConfiguration
或 AbstractReactiveElasticsearchConfiguration
像 Elasticsearch Clients 中描述的那样配置 Spring Data Elasticsearch 时,刷新策略将被初始化为 null
。以前响应式代码将其初始化为 IMMEDIATE
,现在响应式和非响应式代码显示相同的行为。
从 4.2.x 升级到 4.3.x
本节介绍从版本 4.2.x 到 4.3.x 的重大更改,以及如何用新引入的功能替换已删除的功能。
Elasticsearch 正在开发一个新的客户端,它将取代 Spring Data Elasticsearch 还在其 API 类和方法中删除或替换了 使用不易替换的类的地方,这种用法被标记为已弃用,我们正在努力进行替换。 查看 弃用 和 Breaking Changes 部分了解更多详细信息。 |
弃用
建议方法
在 SearchOperations
以及 ElasticsearchOperations
中,以 org.elasticsearch.search.suggest.SuggestBuilder
作为参数并返回 org.elasticsearch.action.search.SearchResponse
的 suggest
方法已被弃用。使用 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,Query
的 fields
属性被解释并添加到 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
。
从 4.3.x 升级到 4.4.x
本节介绍从版本 4.3.x 到 4.4.x 的重大更改以及如何用新引入的功能替换已删除的功能。
重大变更
删除已弃用的类
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
类已被删除。这是使用 TransportClient
的 ElasticsearchOperations
接口的实现。必须使用命令式 ElasticsearchRestTemplate
或响应式 ReactiveElasticsearchTemplate
连接到 Elasticsearch。
包更改
在 4.3 中,两个类(ElasticsearchAggregations
和 ElasticsearchAggregation
)已移至 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 名称 elasticsearchOperations 和 elasticsearchTemplate,它使用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 名称 reactiveElasticsearchOperations 和 reactiveElasticsearchTemplate,它使用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 客户端
重大变更
包更改
所有使用或依赖于已弃用的 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.HttpHeaders
的 addAll()
方法。
新的 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 名称 elasticsearchOperations 和 elasticsearchTemplate,它使用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 名称 reactiveElasticsearchOperations 和 reactiveElasticsearchTemplate,它使用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,而这不存在。