Hibernate 搜索简介
1. 概述
在本文中,我们将讨论 Hibernate Search 的基础知识,如何配置它,我们将实现一些简单的查询。
2. Hibernate Search 基础
每当我们必须实现全文搜索功能时,使用我们已经精通的工具总是一个加分项。
如果我们已经将 Hibernate 和 JPA 用于 ORM,那么我们离 Hibernate Search 仅一步之遥。
Hibernate Search 集成了 Apache Lucene,这是一个用 Java 编写的高性能和可扩展的全文搜索引擎库。这结合了 Lucene 的强大功能与 Hibernate 和 JPA 的简单性。
简单地说,我们只需要在我们的领域类中添加一些额外的注解,该工具就会处理数据库/索引同步之类的事情。
Hibernate Search 还提供了 Elasticsearch 集成;但是,由于它仍处于试验阶段,因此我们将在这里重点关注 Lucene。
3. 配置
3.1. Maven 依赖项
在开始之前,我们首先需要将必要的依赖 项添加到我们的pom.xml中:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>5.8.2.Final</version>
</dependency>
为简单起见,我们将使用H2 作为我们的数据库:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
3.2. 配置
我们还必须指定 Lucene 应该在哪里存储索引。
这可以通过属性hibernate.search.default.directory_provider来完成。
我们将选择filesystem,这是我们用例中最直接的选项。更多选项在官方文档 中列出。Filesystem-master/filesystem-slave和infinispan对于集群应用程序来说是值得注意的,其中索引必须在节点之间同步。
我们还必须定义一个默认的基本目录来存储索引:
hibernate.search.default.directory_provider = filesystem
hibernate.search.default.indexBase = /data/index/default
4. 模型类
配置完成后,我们现在可以指定我们的模型了。
*在 JPA 注解@Entity和@Table之上,我们必须添加一个@Indexed注解。**它告诉 Hibernate Search 实体Product*应该被索引。
之后,我们必须通过添加@Field*注解*将所需的属性定义为可搜索的:
@Entity
@Indexed
@Table(name = "product")
public class Product {
@Id
private int id;
@Field(termVector = TermVector.YES)
private String productName;
@Field(termVector = TermVector.YES)
private String description;
@Field
private int memory;
// getters, setters, and constructors
}
稍后“More Like This”查询将需要termVector = TermVector.YES属性。
5. 构建 Lucene 索引
在开始实际查询之前,我们必须先触发 Lucene 来构建索引:
FullTextEntityManager fullTextEntityManager
= Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();
在这个初始构建之后,Hibernate Search 将负责保持索引是最新的。IE。我们可以像往常一样通过EntityManager创建、操作和删除实体。
6. 构建和执行查询
现在,我们准备好创建我们的第一个查询。
在下一节中,我们将展示准备和执行查询的一般工作流程。
之后,我们将为最重要的查询类型创建一些示例查询。
6.1. 创建和执行查询的一般工作流程
准备和执行查询通常包括四个步骤:
在第 1 步中,我们必须获取 JPA FullTextEntityManager并从中获取QueryBuilder:
FullTextEntityManager fullTextEntityManager
= Search.getFullTextEntityManager(entityManager);
QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(Product.class)
.get();
在第 2 步中,我们将通过 Hibernate 查询 DSL 创建一个 Lucene 查询:
org.apache.lucene.search.Query query = queryBuilder
.keyword()
.onField("productName")
.matching("iphone")
.createQuery();
在第 3 步中,我们将 Lucene 查询包装到 Hibernate 查询中:
org.hibernate.search.jpa.FullTextQuery jpaQuery
= fullTextEntityManager.createFullTextQuery(query, Product.class);
最后,在第 4 步中,我们将执行查询:
List<Product> results = jpaQuery.getResultList();
注意:默认情况下,Lucene 按相关性对结果进行排序。
对于所有查询类型,步骤 1、3 和 4 都是相同的。
下面,我们将重点介绍第二步,即如何创建不同类型的查询。
6.2. 关键字查询
最基本的用例是搜索特定的单词。
这就是我们在上一节中实际所做的:
Query keywordQuery = queryBuilder
.keyword()
.onField("productName")
.matching("iphone")
.createQuery();
在这里,*keyword()*指定我们正在寻找一个特定的词,*onField()*告诉 Lucene 去哪里寻找,*matching()*去寻找什么。
6.3. 模糊查询
模糊查询就像关键字查询一样工作,除了我们可以定义“模糊”的限制,超过这个限制 Lucene 将接受这两个术语作为匹配。
通过withEditDistanceUpTo(),我们可以定义一个术语可能偏离另一个术语的程度。它可以设置为 0、1 和 2,其中默认值为 2(注意:此限制来自 Lucene 的实现)。
通过withPrefixLength(),我们可以定义应被模糊性忽略的前缀长度:
Query fuzzyQuery = queryBuilder
.keyword()
.fuzzy()
.withEditDistanceUpTo(2)
.withPrefixLength(0)
.onField("productName")
.matching("iPhaen")
.createQuery();
6.4. 通配符查询
Hibernate Search 还使我们能够执行通配符查询,即某个单词的一部分未知的查询。
为此,我们可以使用“ ?” 对于单个字符,“ * ”对于任何字符序列:
Query wildcardQuery = queryBuilder
.keyword()
.wildcard()
.onField("productName")
.matching("Z*")
.createQuery();
6.5.短语查询
如果我们想搜索多个单词,我们可以使用词组查询。如有必要,我们可以使用*phrase()和withSlop()*来寻找**准确或近似的句子。**slop 因子定义了句子中允许的其他单词的数量:
Query phraseQuery = queryBuilder
.phrase()
.withSlop(1)
.onField("description")
.sentence("with wireless charging")
.createQuery();
6.6. 简单查询字符串查询
对于之前的查询类型,我们必须明确指定查询类型。
如果我们想给用户更多的权力,我们可以使用简单的查询字符串查询:这样,他可以在运行时定义自己的查询。
支持以下查询类型:
- 布尔值(AND 使用“+”,或使用“|”,不使用“-”)
- 前缀(前缀*)
- 短语(“一些短语”)
- 优先级(使用括号)
- 模糊(模糊~2)
- 短语查询的近运算符(“一些短语”~3)
以下示例将结合模糊、短语和布尔查询:
Query simpleQueryStringQuery = queryBuilder
.simpleQueryString()
.onFields("productName", "description")
.matching("Aple~2 + \"iPhone X\" + (256 | 128)")
.createQuery();
6.7. 范围查询
范围查询搜索给定边界之间的值。这可以应用于数字、日期、时间戳和字符串:
Query rangeQuery = queryBuilder
.range()
.onField("memory")
.from(64).to(256)
.createQuery();
6.8. 更多类似的查询
我们的最后一个查询类型是“ More Like This ”——查询。为此,我们提供了一个实体,Hibernate Search 返回一个包含相似实体的列表,每个实体都有一个相似度分数。
如前所述,我们模型类中的termVector = TermVector.YES属性对于这种情况是必需的:它告诉 Lucene 在索引期间存储每个术语的频率。
基于此,将在查询执行时计算相似度:
Query moreLikeThisQuery = queryBuilder
.moreLikeThis()
.comparingField("productName").boostedTo(10f)
.andField("description").boostedTo(1f)
.toEntity(entity)
.createQuery();
List<Object[]> results = (List<Object[]>) fullTextEntityManager
.createFullTextQuery(moreLikeThisQuery, Product.class)
.setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE)
.getResultList();
6.9. 搜索多个字段
到目前为止,我们只使用*onField()*创建了用于搜索一个属性的查询。
根据用例,我们还可以搜索两个或多个属性:
Query luceneQuery = queryBuilder
.keyword()
.onFields("productName", "description")
.matching(text)
.createQuery();
此外,我们可以指定要单独搜索的每个属性,例如,如果我们想为一个属性定义一个提升:
Query moreLikeThisQuery = queryBuilder
.moreLikeThis()
.comparingField("productName").boostedTo(10f)
.andField("description").boostedTo(1f)
.toEntity(entity)
.createQuery();
6.10. 组合查询
最后,Hibernate Search 还支持使用各种策略组合查询:
- SHOULD:查询应该包含子查询的匹配元素
- MUST:查询必须包含子查询的匹配元素
- MUST NOT:查询不能包含子查询的匹配元素
聚合类似于布尔值AND、OR和NOT。但是,名称不同以强调它们也对相关性产生影响。
例如,两个查询之间的SHOULD类似于布尔OR:如果两个查询之一有匹配,则将返回此匹配。
但是,如果两个查询都匹配,则与只有一个查询匹配的情况相比,匹配将具有更高的相关性:
Query combinedQuery = queryBuilder
.bool()
.must(queryBuilder.keyword()
.onField("productName").matching("apple")
.createQuery())
.must(queryBuilder.range()
.onField("memory").from(64).to(256)
.createQuery())
.should(queryBuilder.phrase()
.onField("description").sentence("face id")
.createQuery())
.must(queryBuilder.keyword()
.onField("productName").matching("samsung")
.createQuery())
.not()
.createQuery();