在独立模式下使用 Hibernate Search 与 Elasticsearch/OpenSearch
您有一个 Quarkus 应用程序?您想为用户提供功能齐全的全文搜索?您来对地方了。
通过本指南,您将了解到如何快速将实体索引到 Elasticsearch 或 OpenSearch 集群中。我们还将探讨如何使用 Hibernate Search API 查询您的 Elasticsearch 或 OpenSearch 集群。
如果您想索引 Hibernate ORM 实体,请参阅 此专用指南。 |
先决条件
要完成本指南,您需要
-
大约 20 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
一个正常工作的容器运行时(Docker 或 Podman)
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 存储库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载一个 存档。
解决方案位于 hibernate-search-standalone-elasticsearch-quickstart
目录。
提供的解决方案包含一些额外的元素,例如测试和测试基础架构。 |
创建 Maven 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数括在双引号中,例如"-DprojectArtifactId=hibernate-search-standalone-elasticsearch-quickstart"
此命令生成一个 Maven 结构,导入以下扩展
-
Hibernate Search Standalone + Elasticsearch,
-
Quarkus REST(以前称为 RESTEasy Reactive)和 Jackson。
如果您已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令将 hibernate-search-standalone-elasticsearch
扩展添加到您的项目中
quarkus extension add hibernate-search-standalone-elasticsearch
./mvnw quarkus:add-extension -Dextensions='hibernate-search-standalone-elasticsearch'
./gradlew addExtension --extensions='hibernate-search-standalone-elasticsearch'
这会将以下内容添加到您的 pom.xml
中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-search-standalone-elasticsearch</artifactId>
</dependency>
implementation("io.quarkus:quarkus-hibernate-search-standalone-elasticsearch")
创建基本类
首先,让我们在 model
子包中创建 Book
和 Author
类。
package org.acme.hibernate.search.elasticsearch.model;
import java.util.List;
import java.util.Objects;
public class Author {
public UUID id; (1)
public String firstName;
public String lastName;
public List<Book> books;
public Author(UUID id, String firstName, String lastName, List<Book> books) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.books = books;
}
}
1 | 我们在这里使用公共字段,因为它们更短,并且对于本质上是数据类的字段没有封装的要求。 但是,如果您更喜欢使用私有字段和 getter/setter,那也是完全可以的,并且只要 getter/setter 符合 JavaBeans 命名约定( |
package org.acme.hibernate.search.elasticsearch.model;
import java.util.Objects;
public class Book {
public UUID id;
public String title;
public Book(UUID id, String title) {
this.id = id;
this.title = title;
}
}
使用 Hibernate Search 注解
为我们的类启用全文搜索功能非常简单,只需添加一些注解即可。
让我们编辑 Author
实体以包含以下内容
package org.acme.hibernate.search.elasticsearch.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IdProjection;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.SearchEntity;
@SearchEntity (1)
@Indexed (2)
public class Author {
@DocumentId (3)
public UUID id;
@FullTextField(analyzer = "name") (4)
@KeywordField(name = "firstName_sort", sortable = Sortable.YES, normalizer = "sort") (5)
public String firstName;
@FullTextField(analyzer = "name")
@KeywordField(name = "lastName_sort", sortable = Sortable.YES, normalizer = "sort")
public String lastName;
@IndexedEmbedded (6)
public List<Book> books = new ArrayList<>();
public Author(UUID id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
@ProjectionConstructor (7)
public Author(@IdProjection UUID id, String firstName, String lastName, List<Book> books) {
this( id, firstName, lastName );
this.books = books;
}
}
1 | 首先,我们将 Author 类型标记为 实体类型。简而言之,这意味着 Author 类型具有自己的、不同的生命周期(不与另一个类型绑定),并且每个 `BookAuthor` 实例都包含一个不可变的、唯一的标识符。 |
2 | 然后,让我们使用 @Indexed 注解将我们的 Author 实体注册为全文索引的一部分。 |
3 | 最后,我们通过定义文档标识符来完成强制配置。 |
4 | @FullTextField 注解声明了一个专门为全文搜索量身定制的索引字段。特别是,我们必须定义一个分析器来拆分和分析 token(~单词)——稍后会详细介绍。 |
5 | 正如您所见,我们可以为同一个属性定义多个字段。这里,我们定义了一个具有特定名称的 @KeywordField 。主要区别在于关键字字段不会被分词(字符串被保留为一个单独的 token),但可以被规范化(即过滤)——稍后会详细介绍。此字段被标记为可排序,因为我们的目的是使用它来对作者进行排序。 |
6 | @IndexedEmbedded 的目的是将 Book 字段包含在 Author 索引中。在这种情况下,我们只使用默认配置:所有关联的 Book 实例的字段都包含在索引中(即 title 字段)。@IndexedEmbedded 还支持嵌套文档(使用 structure = NESTED 属性),但这里我们不需要。您还可以通过 includePaths /excludePaths 属性指定要嵌入到父索引中的字段,如果您不想要所有字段的话。 |
7 | 我们将一个(单个)构造函数标记为 @ProjectionConstructor ,以便可以从索引内容中重建 Author 实例。 |
现在我们的作者已被索引,我们将需要映射书籍,以便此 @IndexedEmbedded
注解实际上嵌入了*一些内容*。
打开 Book
类并包含以下内容
package org.acme.hibernate.search.elasticsearch.model;
import java.util.Objects;
import java.util.UUID;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.SearchEntity;
@SearchEntity (1)
public class Book {
@KeywordField (2)
public UUID id;
@FullTextField(analyzer = "english") (3)
public String title;
@ProjectionConstructor (4)
public Book(UUID id, String title) {
this.id = id;
this.title = title;
}
}
1 | 我们也已将 Book 类型标记为 实体类型,但我们没有使用 @Indexed ,因为我们决定不需要为书籍设置专门的索引。 |
2 | 我们索引书籍的 ID,以便可以对其进行投影(如下所示)。 |
3 | 我们使用与 Author 相同的 @FullTextField ,但您会注意到分析器不同——稍后会详细介绍。 |
4 | 与 Author 一样,我们将一个构造函数标记为 @ProjectionConstructor ,以便可以从索引内容中重建 Book 实例。 |
分析器和规范化器
简介
分析是全文搜索的重要组成部分:它定义了在索引或构建搜索查询时文本如何被处理。
分析器的作用是将文本拆分为 token(~单词)并过滤它们(例如,将其全部转换为小写并删除重音符号)。
规范化器是一种特殊的分析器,它将输入保留为单个 token。它对于排序或索引关键字特别有用。
有很多内置的分析器,但您也可以开发自己的分析器以满足您的特定需求。
您可以在 Elasticsearch 文档的 分析部分 中了解更多关于 Elasticsearch 分析框架的信息。
定义使用的分析器
当我们向实体添加 Hibernate Search 注解时,我们定义了使用的分析器和规范化器。通常
@FullTextField(analyzer = "english")
@FullTextField(analyzer = "name")
@KeywordField(name = "lastName_sort", sortable = Sortable.YES, normalizer = "sort")
我们使用
-
一个名为
name
的分析器用于个人姓名, -
一个名为
english
的分析器用于书名, -
一个名为
sort
的规范化器用于排序字段
但我们还没有设置它们。
让我们看看如何使用 Hibernate Search 来实现它。
设置分析器
这是一项简单的任务,我们只需要创建一个 ElasticsearchAnalysisConfigurer
的实现(并配置 Quarkus 使用它,稍后会详细介绍)。
为了满足我们的要求,让我们创建以下实现
package org.acme.hibernate.search.elasticsearch.config;
import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurationContext;
import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer;
import io.quarkus.hibernate.search.standalone.elasticsearch.SearchExtension;
@SearchExtension (1)
public class AnalysisConfigurer implements ElasticsearchAnalysisConfigurer {
@Override
public void configure(ElasticsearchAnalysisConfigurationContext context) {
context.analyzer("name").custom() (2)
.tokenizer("standard")
.tokenFilters("asciifolding", "lowercase");
context.analyzer("english").custom() (3)
.tokenizer("standard")
.tokenFilters("asciifolding", "lowercase", "porter_stem");
context.normalizer("sort").custom() (4)
.tokenFilters("asciifolding", "lowercase");
}
}
1 | 用 @SearchExtension 限定符注解配置器实现,以告知 Quarkus 它应该用于 Hibernate Search Standalone,默认情况下用于所有 Elasticsearch 索引。该注解还可以针对特定的持久化单元( |
2 | 这是一个简单的分析器,它根据空格拆分单词,通过其 ASCII 对应项删除任何非 ASCII 字符(从而删除重音符号)并将所有内容转换为小写。在我们的示例中,它用于作者的姓名。 |
3 | 我们对这个分析器更具侵略性,它包含一些词干提取:我们将能够搜索 mystery 并获得结果,即使索引的输入包含 mysteries 。对于人名来说,这显然过于激进,但对于书名来说却是完美的。 |
4 | 这是用于排序的规范化器。与我们的第一个分析器非常相似,只是我们不对单词进行分词,因为我们想要一个且只有一个 token。 |
有关配置分析器的更多信息,请参阅参考文档的 此部分。
实现 REST 服务
创建 org.acme.hibernate.search.elasticsearch.LibraryResource
类
package org.acme.hibernate.search.elasticsearch;
import java.util.ArrayList;
import java.util.UUID;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import org.acme.hibernate.search.elasticsearch.model.Author;
import org.acme.hibernate.search.elasticsearch.model.Book;
import org.hibernate.search.mapper.pojo.standalone.mapping.SearchMapping;
import org.hibernate.search.mapper.pojo.standalone.session.SearchSession;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestPath;
@Path("/library")
public class LibraryResource {
@Inject
SearchMapping searchMapping; (1)
@PUT
@Path("author")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addAuthor(@RestForm String firstName, @RestForm String lastName) {
try (var searchSession = searchMapping.createSession()) { (2)
Author author = new Author(UUID.randomUUID(), firstName, lastName, new ArrayList<>());
searchSession.indexingPlan().add(author); (3)
}
}
@GET
@Path("author/{id}")
public Author getAuthor(@RestPath UUID id) {
try (var searchSession = searchMapping.createSession()) {
return getAuthor(searchSession, id);
}
}
private Author getAuthor(SearchSession searchSession, UUID id) {
return searchSession.search(Author.class) (4)
.where(f -> f.id().matching(id))
.fetchSingleHit()
.orElseThrow(NotFoundException::new);
}
@POST
@Path("author/{id}")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void updateAuthor(@RestPath UUID id, @RestForm String firstName, @RestForm String lastName) {
try (var searchSession = searchMapping.createSession()) {
Author author = getAuthor(searchSession, id); (5)
author.firstName = firstName;
author.lastName = lastName;
searchSession.indexingPlan().addOrUpdate(author); (5)
}
}
@DELETE
@Path("author/{id}")
public void deleteAuthor(@RestPath UUID id) {
try (var searchSession = searchMapping.createSession()) {
searchSession.indexingPlan().purge(Author.class, id, null); (6)
}
}
@PUT
@Path("author/{authorId}/book/")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addBook(@RestPath UUID authorId, @RestForm String title) {
try (var searchSession = searchMapping.createSession()) {
Author author = getAuthor(searchSession, authorId); (7)
author.books.add(new Book(authorId, title));
searchSession.indexingPlan().addOrUpdate(author);
}
}
@DELETE
@Path("author/{authorId}/book/{bookId}")
public void deleteBook(@RestPath UUID authorId, @RestPath UUID bookId) {
try (var searchSession = searchMapping.createSession()) {
Author author = getAuthor(searchSession, authorId); (7)
author.books.removeIf(book -> book.id.equals(bookId));
searchSession.indexingPlan().addOrUpdate(author);
}
}
}
1 | 注入 Hibernate Search 映射,这是 Hibernate Search API 的主要入口点。 |
2 | 创建 Hibernate Search 会话,允许在索引上执行操作。 |
3 | 要索引新作者,请检索会话的索引计划并调用 add ,将作者实例作为参数传入。 |
4 | 要从索引中检索作者,请执行简单的搜索——稍后将详细介绍——按标识符搜索。 |
5 | 要更新作者,请从索引中检索它,应用更改,检索会话的索引计划并调用 addOrUpdate ,将作者实例作为参数传入。 |
6 | 要按标识符删除作者,请检索会话的索引计划并调用 purge ,将作者类和标识符作为参数传入。 |
7 | 由于书籍由作者“拥有”(它们会为每个作者复制,并且它们的生命周期与作者绑定),因此添加/删除书籍只是对作者的更新。 |
这里没有什么突破性的:只是在 REST 服务中使用 Hibernate Search API 进行了一些 CRUD 操作。
有趣的部分在于添加了一个搜索端点。在我们的 LibraryResource
中,我们只需要添加以下方法(以及一些 import
语句)
@GET
@Path("author/search")
public List<Author> searchAuthors(@RestQuery String pattern, (1)
@RestQuery Optional<Integer> size) {
try (var searchSession = searchMapping.createSession()) { (2)
return searchSession.search(Author.class) (3)
.where(f -> pattern == null || pattern.isBlank()
? f.matchAll() (4)
: f.simpleQueryString()
.fields("firstName", "lastName", "books.title").matching(pattern)) (5)
.sort(f -> f.field("lastName_sort").then().field("firstName_sort")) (6)
.fetchHits(size.orElse(20)); (7)
}
}
1 | 使用 org.jboss.resteasy.reactive.RestQuery 注解类型来避免重复参数名。 |
2 | 创建 Hibernate Search 会话,允许在索引上执行操作。 |
3 | 我们指示我们正在搜索 Author 。 |
4 | 我们创建一个谓词:如果模式为空,我们使用 matchAll() 谓词。 |
5 | 如果我们有一个有效的模式,我们将创建一个 simpleQueryString() 谓词,作用于 firstName 、lastName 和 books.title 字段,以匹配我们的模式。 |
6 | 我们定义了结果的排序顺序。这里我们按姓氏排序,然后按名字排序。请注意,我们使用了为排序创建的特定字段。 |
7 | 获取 size 个热门命中,默认为 20 。显然,也支持分页。 |
Hibernate Search DSL 支持 Elasticsearch 谓词的很大一部分(match、range、nested、phrase、spatial 等)。可以自由使用自动补全来探索 DSL。 当这还不够时,您始终可以 直接使用 JSON 定义谓词。 |
自动数据初始化
为了演示目的,让我们导入初始数据集。
让我们在 LibraryResource
中添加一些方法
void onStart(@Observes StartupEvent ev) { (1)
// Index some test data if nothing exists
try (var searchSession = searchMapping.createSession()) {
if (0 < searchSession.search(Author.class) (2)
.where(f -> f.matchAll())
.fetchTotalHitCount()) {
return;
}
for (Author author : initialDataSet()) { (3)
searchSession.indexingPlan().add(author); (4)
}
}
}
private List<Author> initialDataSet() {
return List.of(
new Author(UUID.randomUUID(), "John", "Irving",
List.of(
new Book(UUID.randomUUID(), "The World According to Garp"),
new Book(UUID.randomUUID(), "The Hotel New Hampshire"),
new Book(UUID.randomUUID(), "The Cider House Rules"),
new Book(UUID.randomUUID(), "A Prayer for Owen Meany"),
new Book(UUID.randomUUID(), "Last Night in Twisted River"),
new Book(UUID.randomUUID(), "In One Person"),
new Book(UUID.randomUUID(), "Avenue of Mysteries"))),
new Author(UUID.randomUUID(), "Paul", "Auster",
List.of(
new Book(UUID.randomUUID(), "The New York Trilogy"),
new Book(UUID.randomUUID(), "Mr. Vertigo"),
new Book(UUID.randomUUID(), "The Brooklyn Follies"),
new Book(UUID.randomUUID(), "Invisible"),
new Book(UUID.randomUUID(), "Sunset Park"),
new Book(UUID.randomUUID(), "4 3 2 1"))));
}
1 | 添加一个将在应用程序启动时执行的方法。 |
2 | 检查索引中是否已存在数据——如果不存在,则退出。 |
3 | 生成初始数据集。 |
4 | 对于每个作者,将其添加到索引中。 |
配置应用程序
与往常一样,我们可以在 Quarkus 配置文件 application.properties
中配置所有内容。
编辑 src/main/resources/application.properties
并注入以下配置
quarkus.hibernate-search-standalone.mapping.structure=document (1)
quarkus.hibernate-search-standalone.elasticsearch.version=9 (2)
quarkus.hibernate-search-standalone.indexing.plan.synchronization.strategy=sync (3)
%prod.quarkus.hibernate-search-standalone.elasticsearch.hosts=localhost:9200 (4)
1 | 我们需要告知 Hibernate Search 实体的结构。 在此应用程序中,我们将索引实体(作者)视为“文档”的根:作者“拥有”其通过关联引用的书籍,这些书籍*不能*独立于作者进行更新。 有关其他选项和更多详细信息,请参阅 |
2 | 我们需要告知 Hibernate Search 我们将使用的 Elasticsearch 版本。 这很重要,因为 Elasticsearch 映射语法因版本而异。由于映射是在构建时创建以减少启动时间,因此 Hibernate Search 无法连接到集群来自动检测版本。请注意,对于 OpenSearch,您需要将版本前缀为 |
3 | 这意味着我们在完成写入之前等待实体可搜索。在生产环境中,write-sync 默认值将提供更好的性能。使用 sync 对于测试尤其重要,因为您需要实体立即可搜索。 |
4 | 对于开发和测试,我们依赖 Dev Services,这意味着 Quarkus 将自动启动一个 Elasticsearch 集群。但在生产模式下,我们将希望手动启动一个 Elasticsearch 集群,这就是为什么我们在 prod 配置文件(%prod. 前缀)中为 Quarkus 提供此连接信息。 |
由于我们依赖 Dev Services,Elasticsearch 模式将在每次应用程序启动时(在测试和开发模式下)自动删除并重新创建(除非已显式设置 如果您出于某种原因无法使用 Dev Services,您将需要设置以下属性以获得类似的行为
|
有关 Hibernate Search Standalone 扩展配置的更多信息,请参阅 配置参考。 |
创建一个前端
现在让我们添加一个简单的网页来与我们的 LibraryResource
进行交互。Quarkus 会自动提供位于 META-INF/resources
目录下的静态资源。在 src/main/resources/META-INF/resources
目录中,用此 index.html 文件中的内容覆盖现有的 index.html
文件。
准备好玩您的应用程序
您现在可以与您的 REST 服务交互
-
使用以下命令启动您的 Quarkus 应用程序
CLIquarkus dev
Maven./mvnw quarkus:dev
Gradle./gradlew --console=plain quarkusDev
-
在浏览器中打开
https://:8080/
-
搜索作者或书名(我们已经为您初始化了一些数据)
-
创建新的作者和书籍,并搜索它们
如您所见,您的所有更新都会自动同步到 Elasticsearch 集群。
构建本机可执行文件
您可以使用常用命令构建本机可执行文件
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
与原生可执行文件编译一样,此操作会消耗大量内存。 在构建原生可执行文件时停止这两个容器,并在完成后再次启动它们可能会更安全。 |
运行它就像执行 ./target/hibernate-search-standalone-elasticsearch-quickstart-1.0.0-SNAPSHOT-runner
一样简单。
然后,您可以将浏览器指向 https://:8080/
并使用您的应用程序。
启动速度比平时稍慢:这主要是因为我们在每次启动时都会删除并重新创建 Elasticsearch 映射。我们还索引了一些初始数据。 在实际应用程序中,这显然不是您每次启动都会做的事情。 |
Dev Services(无配置数据存储)
Quarkus 支持一项称为 Dev Services 的功能,该功能允许您启动各种容器而无需任何配置。
对于 Elasticsearch,此支持扩展到默认的 Elasticsearch 连接。实际这意味着,如果您未配置 quarkus.hibernate-search-standalone.elasticsearch.hosts
,Quarkus 将在运行测试或在 开发模式 下自动启动 Elasticsearch 容器,并自动配置连接。
运行应用程序的生产版本时,需要像往常一样配置 Elasticsearch 连接,因此如果您想在 application.properties
中包含生产数据库配置并继续使用 Dev Services,我们建议您使用 %prod.
配置文件来定义您的 Elasticsearch 设置。
Elasticsearch 的 Dev Services 目前无法同时启动多个集群,因此它只能与默认持久化单元的默认后端一起使用:命名的持久化单元或命名的后端将无法利用 Elasticsearch 的 Dev Services。 |
有关更多信息,您可以阅读 Elasticsearch 开发服务指南。
程序化映射
如果由于某种原因无法将 Hibernate Search 注解添加到实体,则可以改用程序化映射。程序化映射是通过通过映射配置器(HibernateOrmSearchMappingConfigurer
)公开的 ProgrammaticMappingConfigurationContext
进行配置的。
映射配置器( |
以下是一个应用了程序化映射的映射配置器示例
package org.acme.hibernate.search.elasticsearch.config;
import org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurationContext;
import org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurer;
import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep;
import io.quarkus.hibernate.search.standalone.elasticsearch.SearchExtension;
@SearchExtension (1)
public class CustomMappingConfigurer implements StandalonePojoMappingConfigurer {
@Override
public void configure(StandalonePojoMappingConfigurationContext context) {
TypeMappingStep type = context.programmaticMapping() (2)
.type(SomeIndexedEntity.class); (3)
type.searchEntity(); (4)
type.indexed() (5)
.index(SomeIndexedEntity.INDEX_NAME); (6)
type.property("id").documentId(); (7)
type.property("text").fullTextField(); (8)
}
}
1 | 用 @SearchExtension 限定符注解配置器实现,以告知 Quarkus 它应该被 Hibernate Search Standalone 使用。 |
2 | 访问程序化映射上下文。 |
3 | 为 SomeIndexedEntity 类型创建映射步骤。 |
4 | 将 SomeIndexedEntity 定义为 Hibernate Search 的实体类型。 |
5 | 将 SomeIndexedEntity 实体定义为已索引。 |
6 | 提供要为 SomeIndexedEntity 实体使用的索引名称。 |
7 | 定义文档 ID 属性。 |
8 | 为 text 属性定义一个全文搜索字段。 |
OpenSearch 兼容性
Hibernate Search 与 Elasticsearch 和 OpenSearch 都兼容,但默认情况下它假定与 Elasticsearch 集群一起工作。
要使 Hibernate Search 与 OpenSearch 集群一起工作,请将配置的版本前缀为 opensearch:
,如下所示。
quarkus.hibernate-search-standalone.elasticsearch.version=opensearch:3.0
所有其他配置选项和 API 与 Elasticsearch 完全相同。
您可以在 Hibernate Search 的参考文档的 此部分 中找到有关兼容的 Elasticsearch 发行版和版本的更多信息。
CDI 集成
插入自定义组件
Hibernate Search Standalone 的 Quarkus 扩展将自动将带有 @SearchExtension
注解的组件注入到 Hibernate Search 中。
该注解可以选择性地针对特定的后端(@SearchExtension(backend = "nameOfYourBackend")
)、索引(@SearchExtension(index = "nameOfYourIndex")
)或它们的组合(@SearchExtension(backend = "nameOfYourBackend", index = "nameOfYourIndex")
),当它对注入的组件类型有意义时。
此功能可用于以下组件类型
org.hibernate.search.engine.reporting.FailureHandler
-
一个组件,应被通知后台进程(主要是索引操作)中发生的任何故障。
范围:每个应用程序一个。
有关更多信息,请参阅参考文档的这一部分。
org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurer
-
一个用于配置 Hibernate Search 映射的组件,特别是程序化配置。
范围:每个持久化单元一个或多个。
有关更多信息,请参阅 本指南的此部分。
org.hibernate.search.mapper.pojo.work.IndexingPlanSynchronizationStrategy
-
一个用于配置应用程序线程和索引之间如何同步的组件。
范围:每个应用程序一个。
有关更多信息,请参阅参考文档的这一部分。
org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer
-
一个用于配置全文分析(例如,分析器、规范化器)的组件。
范围:每个后端一个或多个。
有关更多信息,请参阅 本指南的此部分。
org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy
-
一个用于配置 Elasticsearch 布局的组件:索引名称、索引别名……
范围:每个后端一个。
有关更多信息,请参阅参考文档的此章节。
离线启动
默认情况下,Hibernate Search 在启动时会向 Elasticsearch 集群发送一些请求。如果 Elasticsearch 集群在 Hibernate Search 启动时并非一定处于运行状态,这可能会导致启动失败。
为了解决这个问题,您可以配置 Hibernate Search 在启动时不要发送任何请求
-
通过将配置属性
quarkus.hibernate-search-standalone.elasticsearch.version-check.enabled
设置为false
来禁用启动时的 Elasticsearch 版本检查。 -
通过将配置属性
quarkus.hibernate-search-standalone.schema-management.strategy
设置为none
来禁用启动时的模式管理。
当然,即使有此配置,在 Elasticsearch 集群变得可访问之前,Hibernate Search 仍然无法索引任何内容或运行搜索查询。
如果您通过将 有关更多信息,请参阅参考文档的 此部分。 |
加载
作为使用 Elasticsearch 作为主要数据存储的替代方法,此扩展还可以用于索引来自其他数据存储的实体。
在这种情况下,您需要将 |
为此,需要从其他数据存储加载实体,并且必须显式实现这种加载。
您可以参考 Hibernate Search 的参考文档以获取有关配置加载的更多信息
在 Quarkus 中,Hibernate Search 参考文档中提到的实体加载器可以定义为 CDI bean,但仍然需要使用 |
管理端点
Hibernate Search 的管理端点是预览版。 在预览版中,向后兼容性和在生态系统中的存在性不受保证。特定改进可能需要更改配置或 API,甚至存储格式,并且正在计划成为稳定版。欢迎在我们的 邮件列表 或我们的 GitHub 问题跟踪器 中提出问题。 |
Hibernate Search 扩展提供了一个 HTTP 端点,用于通过 管理界面 重新索引数据。默认情况下,此端点不可用。可以通过配置属性启用它,如下所示。
quarkus.management.enabled=true (1)
quarkus.hibernate-search-standalone.management.enabled=true (2)
1 | 启用 管理界面。 |
2 | 启用 Hibernate Search Standalone 特定的管理端点。 |
启用管理端点后,可以通过 /q/hibernate-search/standalone/reindex
重新索引数据,其中 /q
是默认的管理根路径,/hibernate-search/standalone/
是默认的 Hibernate Search 根管理路径。它可以(/hibernate-search/standalone/
)通过配置属性更改,如下所示。
quarkus.hibernate-search-standalone.management.root-path=custom-root-path (1)
1 | 为 Hibernate Search 的管理端点使用自定义的 custom-root-path 路径。如果使用默认管理根路径,则重新索引路径将变为 /q/custom-root-path/reindex 。 |
此端点仅接受 POST
请求,且 application/json
内容类型。如果提交了空请求体,将重新索引所有索引实体。
要重新索引实体类型,需要将其配置为从外部源加载。 没有该配置,通过管理端点(或任何其他 API)进行的重新索引将失败。 |
如果只需要重新索引部分实体,或者需要自定义底层批量索引器的配置,则可以通过请求体传递此信息,如下所示。
{
"filter": {
"types": ["EntityName1", "EntityName2", "EntityName3", ...], (1)
},
"massIndexer":{
"typesToIndexInParallel": 1, (2)
}
}
1 | 一组要重新索引的实体名称。如果未指定或为空,将重新索引所有实体类型。 |
2 | 设置并行索引的实体类型数量。 |
下面示例中列出了所有可能的过滤器和可用的批量索引器配置。
{
"filter": { (1)
"types": ["EntityName1", "EntityName2", "EntityName3", ...], (2)
"tenants": ["tenant1", "tenant2", ...] (3)
},
"massIndexer":{ (4)
"typesToIndexInParallel": 1, (5)
"threadsToLoadObjects": 6, (6)
"batchSizeToLoadObjects": 10, (7)
"cacheMode": "IGNORE", (8)
"mergeSegmentsOnFinish": false, (9)
"mergeSegmentsAfterPurge": true, (10)
"dropAndCreateSchemaOnStart": false, (11)
"purgeAllOnStart": true, (12)
"idFetchSize": 100, (13)
"transactionTimeout": 100000, (14)
}
}
1 | 用于限制重新索引范围的过滤器对象。 |
2 | 一组要重新索引的实体名称。如果未指定或为空,将重新索引所有实体类型。 |
3 | 一个租户 ID 数组,用于多租户场景。如果未指定或为空,将重新索引所有租户。 |
4 | 批量索引器配置对象。 |
5 | 设置并行索引的实体类型数量。 |
6 | 设置用于加载根实体的线程数。 |
7 | 设置用于加载根实体的批量大小。 |
8 | 设置数据加载任务的缓存交互模式。 |
9 | 索引完成后,每个索引是否合并为单个段。 |
10 | 在初始索引清除之后、索引之前,每个索引是否合并为单个段。 |
11 | 在索引之前,是否删除索引及其模式(如果存在)。 |
12 | 在索引之前,是否从索引中删除所有实体。 |
13 | 指定加载主键时要使用的获取大小(如果对象需要被索引)。 |
14 | 指定用于加载 ID 和要重新索引的实体事务的超时时间。 请注意,JSON 中的所有属性都是可选的,并且只需要使用需要的属性。 |
有关批量索引器配置的更多详细信息,请参阅 Hibernate Search 参考文档的 相应部分。
提交重新索引请求将触发后台索引。批量索引进度将显示在应用程序日志中。为了测试目的,了解索引何时完成可能很有用。在 URL 中添加 wait_for=finished
查询参数将导致管理端点返回一个分块响应,该响应将报告索引何时开始以及何时完成。
限制
-
Hibernate Search Standalone 扩展不能与 带有 Hibernate ORM 的 Hibernate Search 扩展 在同一个应用程序中使用
请参阅 #39517 来跟踪进度。
-
目前 AWS 请求签名不可用,不像 带有 Hibernate ORM 的 Hibernate Search 扩展
请参阅 #26991 来跟踪进度。
-
目前乐观并发控制不可用。
请参阅 HSEARCH-5105 来跟踪进度。
-
Elasticsearch/OpenSearch 不支持事务,因此多文档更新可能会部分失败,使索引处于不一致状态。
这无法像 带有 Hibernate ORM 的 Hibernate Search 扩展 中通过 邮箱轮询协调 那样进行规避,因为该协调需要 Hibernate ORM 并依赖于数据源自(事务性)关系数据库的事实。
常见问题解答
为什么只支持 Elasticsearch?
Hibernate Search 支持 Lucene 后端和 Elasticsearch 后端。
在 Quarkus 的背景下,为了构建可扩展的应用程序,我们认为后者更有意义。因此,我们集中精力于它。
目前我们不打算在 Quarkus 中支持 Lucene 后端,尽管 Quarkiverse 中有一个跟踪此类实现进度的 issue:quarkiverse/quarkus-hibernate-search-extras#180。
Hibernate Search Standalone 配置参考
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
是否在*构建期间*启用了 Hibernate Search Standalone。 如果在构建期间禁用了 Hibernate Search,所有与 Hibernate Search 相关的处理都将被跳过,但无法在运行时激活 Hibernate Search: 环境变量: 显示更多 |
布尔值 |
|
||||||||||||||||||||||||||||||
一个bean 引用,指向应被通知后台进程(主要是索引操作)中发生的任何故障的组件。 引用的 bean 必须实现 有关更多信息,请参阅参考文档的这一部分。
环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
一个或多个bean 引用,指向用于配置 Hibernate Search 映射的组件,特别是程序化配置。 引用的 bean 必须实现 有关如何使用映射配置器应用程序化映射的示例,请参阅 程序化映射。
环境变量: 显示更多 |
字符串列表 |
|||||||||||||||||||||||||||||||
Hibernate Search 实体映射的结构。 这必须与使用 Hibernate Search 索引的应用程序模型结构相匹配
另请参阅 环境变量: 显示更多 |
|
|
||||||||||||||||||||||||||||||
Hibernate Search Standalone 在运行时是否应激活。 如果 Hibernate Search Standalone 未激活,它将不会随应用程序启动,并且访问 SearchMapping 进行搜索或其他操作将不可行。 请注意,如果 Hibernate Search Standalone 被禁用(即 环境变量: 显示更多 |
布尔值 |
|
||||||||||||||||||||||||||||||
模式管理策略,控制如何在启动和关闭时创建、更新、验证或删除索引及其模式。 可用值
有关更多信息,请参阅参考文档的 此部分。 环境变量: 显示更多 |
|
|
||||||||||||||||||||||||||||||
如何在应用程序线程和索引之间进行同步,尤其是在依赖于实体更改时(隐式)侦听器触发的索引,以及在使用显式的 定义在 可用值
此属性还接受一个bean 引用,指向 有关更多信息,请参阅参考文档的这一部分。
环境变量: 显示更多 |
字符串 |
|
||||||||||||||||||||||||||||||
类型 |
默认 |
|||||||||||||||||||||||||||||||
集群中使用的 Elasticsearch 版本。 由于 schema 是在没有连接到服务器的情况下生成的,因此此项是必需的。 它不必是精确的版本(例如,它可以是 这里没有经验法则,因为它取决于 Elasticsearch 版本引入的 schema 不兼容性。无论如何,如果出现问题,当 Hibernate Search 尝试连接到集群时,您将收到错误。 环境变量: 显示更多 |
ElasticsearchVersion |
|||||||||||||||||||||||||||||||
classpath 中文件的路径,该文件包含自定义索引设置,以便在创建 Elasticsearch 索引时包含在索引定义中。 提供的设置将与 Hibernate Search 生成的设置合并,包括分析器定义。当通过分析器配置器和这些自定义设置配置分析时,行为是未定义的;不应依赖它。 有关更多信息,请参阅参考文档的此章节。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
classpath 中文件的路径,该文件包含自定义索引映射,以便在创建 Elasticsearch 索引时包含在索引定义中。 该文件不需要(通常也不应该)包含完整的映射:Hibernate Search 将自动在给定的映射中注入缺失的属性(索引字段)。 有关更多信息,请参阅参考文档的此章节。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
一个或多个bean 引用,用于配置全文分析(例如,分析器、规范化器)的组件。 引用的 Bean 必须实现 有关更多信息,请参阅 设置分析器。
环境变量: 显示更多 |
字符串列表 |
|||||||||||||||||||||||||||||||
Elasticsearch 服务器的主机列表。 环境变量: 显示更多 |
字符串列表 |
|
||||||||||||||||||||||||||||||
联系 Elasticsearch 服务器时使用的协议。 设置为 "https" 以启用 SSL/TLS。 环境变量: 显示更多 |
|
|
||||||||||||||||||||||||||||||
用于身份验证的用户名。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
用于身份验证的密码。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
建立与 Elasticsearch 服务器的连接时的超时时间。 环境变量: 显示更多 |
|
|||||||||||||||||||||||||||||||
从 Elasticsearch 服务器读取响应时的超时时间。 环境变量: 显示更多 |
|
|||||||||||||||||||||||||||||||
执行对 Elasticsearch 服务器的请求时的超时时间。 这包括等待连接可用、发送请求和读取响应所需的时间。 环境变量: 显示更多 |
||||||||||||||||||||||||||||||||
到所有 Elasticsearch 服务器的最大连接数。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
每个 Elasticsearch 服务器的最大连接数。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
定义是否启用自动发现。 环境变量: 显示更多 |
布尔值 |
|
||||||||||||||||||||||||||||||
节点列表的刷新间隔。 环境变量: 显示更多 |
|
|||||||||||||||||||||||||||||||
分配给后端的线程池的大小。 请注意,该数字是每个后端,而不是每个索引。添加更多索引不会添加更多线程。 由于此线程池中发生的所有操作都是非阻塞的,将大小增加到 JVM 可用的处理器核心数以上不会带来明显性能提升。唯一需要更改此设置的原因是减少线程数;例如,在一个具有单个索引和单个索引队列、运行在具有 64 个处理器核心的机器上的应用程序中,您可能希望减少线程数。 默认为启动时 JVM 可用的处理器核心数。 环境变量: 显示更多 |
整数 |
|||||||||||||||||||||||||||||||
是否忽略部分分片故障 ( 环境变量: 显示更多 |
布尔值 |
|
||||||||||||||||||||||||||||||
Hibernate Search 是否应在启动时检查 Elasticsearch 集群的版本。 如果 Elasticsearch 集群在启动时可能不可用,请设置为 环境变量: 显示更多 |
布尔值 |
|
||||||||||||||||||||||||||||||
启动时所需的最小Elasticsearch 集群状态。 环境变量: 显示更多 |
|
|
||||||||||||||||||||||||||||||
在引导失败之前,我们应该等待状态多长时间。 环境变量: 显示更多 |
|
|||||||||||||||||||||||||||||||
分配给每个索引的索引队列数。 更高的值将导致更多连接并行使用,这可能提高索引吞吐量,但有使 Elasticsearch 过载的风险,即溢出其 HTTP 请求缓冲区并触发断路器,导致 Elasticsearch 放弃某些请求并导致索引失败。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
索引队列的大小。 较低的值可能导致内存使用量减少,尤其是在有许多队列的情况下,但值太低会降低达到最大批量大小的可能性,并增加应用程序线程因队列已满而阻塞的可能性,这可能导致索引吞吐量降低。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
处理索引队列时创建的批量请求的最大大小。 更高的值将导致每次发送到 Elasticsearch 的 HTTP 请求中包含更多文档,这可能提高索引吞吐量,但有使 Elasticsearch 过载的风险,即溢出其 HTTP 请求缓冲区并触发断路器,导致 Elasticsearch 放弃某些请求并导致索引失败。 请注意,将此数字提高到队列大小以上没有效果,因为批量请求不能包含比队列中包含的请求更多的请求。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
一个bean 引用,用于配置 Elasticsearch 布局:索引名称、索引别名…… 引用的 Bean 必须实现 可用的内置实现
有关更多信息,请参阅参考文档的此章节。
环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
类型 |
默认 |
|||||||||||||||||||||||||||||||
classpath 中文件的路径,该文件包含自定义索引设置,以便在创建 Elasticsearch 索引时包含在索引定义中。 提供的设置将与 Hibernate Search 生成的设置合并,包括分析器定义。当通过分析器配置器和这些自定义设置配置分析时,行为是未定义的;不应依赖它。 有关更多信息,请参阅参考文档的此章节。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
classpath 中文件的路径,该文件包含自定义索引映射,以便在创建 Elasticsearch 索引时包含在索引定义中。 该文件不需要(通常也不应该)包含完整的映射:Hibernate Search 将自动在给定的映射中注入缺失的属性(索引字段)。 有关更多信息,请参阅参考文档的此章节。 环境变量: 显示更多 |
字符串 |
|||||||||||||||||||||||||||||||
一个或多个bean 引用,用于配置全文分析(例如,分析器、规范化器)的组件。 引用的 Bean 必须实现 有关更多信息,请参阅 设置分析器。
环境变量: 显示更多 |
字符串列表 |
|||||||||||||||||||||||||||||||
启动时所需的最小Elasticsearch 集群状态。 环境变量: 显示更多 |
|
|
||||||||||||||||||||||||||||||
在引导失败之前,我们应该等待状态多长时间。 环境变量: 显示更多 |
|
|||||||||||||||||||||||||||||||
分配给每个索引的索引队列数。 更高的值将导致更多连接并行使用,这可能提高索引吞吐量,但有使 Elasticsearch 过载的风险,即溢出其 HTTP 请求缓冲区并触发断路器,导致 Elasticsearch 放弃某些请求并导致索引失败。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
索引队列的大小。 较低的值可能导致内存使用量减少,尤其是在有许多队列的情况下,但值太低会降低达到最大批量大小的可能性,并增加应用程序线程因队列已满而阻塞的可能性,这可能导致索引吞吐量降低。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
处理索引队列时创建的批量请求的最大大小。 更高的值将导致每次发送到 Elasticsearch 的 HTTP 请求中包含更多文档,这可能提高索引吞吐量,但有使 Elasticsearch 过载的风险,即溢出其 HTTP 请求缓冲区并触发断路器,导致 Elasticsearch 放弃某些请求并导致索引失败。 请注意,将此数字提高到队列大小以上没有效果,因为批量请求不能包含比队列中包含的请求更多的请求。 环境变量: 显示更多 |
整数 |
|
||||||||||||||||||||||||||||||
类型 |
默认 |
|||||||||||||||||||||||||||||||
重新索引端点的根路径。此值将解析为相对于 环境变量: 显示更多 |
字符串 |
|
||||||||||||||||||||||||||||||
如果管理界面已开启,则重新索引端点将在管理界面下发布。此属性允许通过将其设置为 环境变量: 显示更多 |
布尔值 |
|
关于 Duration 格式
要写入持续时间值,请使用标准的 您还可以使用简化的格式,以数字开头
在其他情况下,简化格式将被转换为
|
关于 Bean 引用
首先,请注意在配置属性中引用 Bean 是可选的,实际上是不推荐的:您可以通过用 如果您确实想使用字符串值在配置属性中引用 Bean,请知道字符串会被解析;以下是最常见的格式
也接受其他格式,但仅对高级用例有用。有关更多信息,请参阅 Hibernate Search 的参考文档的 此部分。 |