1. 概述

本文将带你快速掌握 Hibernate Search 的核心概念、配置方法,并实现几种常用查询。如果你已经在项目中使用 Hibernate/JPA,那么集成全文搜索功能就差这一步了。

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
hibernate.search.default.indexBase = /data/index/default

这里选择 filesystem 是最直接的方案。集群环境可考虑 filesystem-master/filesystem-slaveinfinispan 实现节点间索引同步(详见官方文档)。

4. 实体类设计

配置完成后,开始定义模型类。在 JPA 注解基础上添加 @Indexed,标记 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
}

⚠️ termVector = TermVector.YES 是后续 "More Like This" 查询的必备条件。

5. 构建 Lucene 索引

执行查询前,必须先触发 Lucene 初始索引构建

FullTextEntityManager fullTextEntityManager 
  = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();

初始构建后,Hibernate Search 会自动维护索引同步。后续通过 EntityManager 进行的增删改操作都会自动更新索引。

⚠️ 踩坑提醒:实体必须完全提交到数据库后才能被 Lucene 发现并索引。这就是为什么测试用例中数据导入通常放在单独的 @Commit 注解测试方法里。

6. 构建与执行查询

6.1. 查询执行四步法

所有查询都遵循这个标准流程:

步骤 1:获取 FullTextEntityManagerQueryBuilder

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:包装成 Hibernate 查询:

org.hibernate.search.jpa.FullTextQuery jpaQuery
  = fullTextEntityManager.createFullTextQuery(query, Product.class);

步骤 4:执行查询:

List<Product> results = jpaQuery.getResultList();

默认按相关性排序。后续重点介绍步骤 2 的不同查询类型。

6.2. 关键词查询

最基础的精确单词搜索

Query keywordQuery = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();
  • keyword():指定精确匹配
  • onField():指定搜索字段
  • matching():指定搜索内容

6.3. 模糊查询

类似关键词查询,但可定义模糊容差

Query fuzzyQuery = queryBuilder
  .keyword()
  .fuzzy()
  .withEditDistanceUpTo(2)
  .withPrefixLength(0)
  .onField("productName")
  .matching("iPhaen")
  .createQuery();
  • withEditDistanceUpTo(2):允许 2 个字符偏差(Lucene 限制最大 2)
  • withPrefixLength(0):前缀 0 个字符参与模糊匹配

6.4. 通配符查询

支持 ?(单字符)和 *(多字符)通配符:

Query wildcardQuery = queryBuilder
  .keyword()
  .wildcard()
  .onField("productName")
  .matching("Z*")
  .createQuery();

6.5. 短语查询

多词搜索,支持精确或近似匹配:

Query phraseQuery = queryBuilder
  .phrase()
  .withSlop(1)
  .onField("description")
  .sentence("with wireless charging")
  .createQuery();
  • withSlop(1):允许短语间插入 1 个其他词

6.6. 简单字符串查询

让用户自定义查询语法,支持:

  • 布尔运算(+ AND, | OR, - NOT)
  • 前缀查询(prefix*
  • 短语查询("some phrase"
  • 优先级(括号)
  • 模糊查询(fuzy~2
  • 近似短语("some phrase"~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. 相似查询

根据给定实体返回相似结果列表,并附带相似度评分:

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();

boostedTo() 用于调整字段权重,termVector = TermVector.YES 是前提条件。

6.9. 多字段搜索

同时搜索多个字段

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. 组合查询

通过布尔策略组合查询:

  • SHOULD:类似 OR,匹配任一子查询即可
  • MUST:类似 AND,必须匹配所有子查询
  • MUST NOT:类似 NOT,不能匹配子查询

组合策略会影响相关性评分,例如两个 SHOULD 查询都匹配时,相关性更高。

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();

7. 总结

本文介绍了 Hibernate Search 的核心概念和主要查询类型实现。更高级的特性可参考官方文档

完整示例代码见 GitHub 仓库


原始标题:Introduction to Hibernate Search