Hibernate Search的使用

章稳
2023-12-01

Hibernate Search

构建查询流程

从FullTextEntityManager获取QueryBuilder

FullTextEntityManager fullTextEntityManager 
  = Search.getFullTextEntityManager(entityManager);
 
QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory() 
  .buildQueryBuilder()
  .forEntity(Product.class)
  .get();

通过Hibernate query DSL创建Lucene查询:

org.apache.lucene.search.Query query = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

包装Luncene查询至Hibernate查询

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

执行查询:

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

关键词查询

Query keywordQuery = queryBuilder
  .keyword()
  .onField("productName")
  .matching("iphone")
  .createQuery();

keyword()指定搜索特定词,onField()告诉Lucene在哪里搜索,matching匹配需要搜索的关键词。

近似查询

近似查询与关键词查询类似,只是可以定义一个“模糊”的限制,在这个限制内Lucene将接受两个术语为匹配结果。

通过ditdistanceupto(),可以定义术语之间的偏离程度。它可以设置为0、1和2,默认值为2(注意:这个限制来自Lucene的实现)。
通过withPrefixLength(),定义前缀的长度,这个长度是由模糊性所忽略的:

Query fuzzyQuery = queryBuilder
  .keyword()
  .fuzzy()
  .withEditDistanceUpTo(2)
  .withPrefixLength(0)
  .onField("productName")
  .matching("iPhaen")
  .createQuery();

通配符查询

Hibernate Search也支持通配符查询,?号表示单个字符,*号表示任意字符:

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

短语搜索

如果想搜索多个词,可以使用短语搜索。使用phrase() 和 withSlop()方法,可以实现精确查询或近似句子。slop因子定义句子中允许其他词数量:

Query phraseQuery = queryBuilder
  .phrase()
  .withSlop(1)
  .onField("description")
  .sentence("with wireless charging")
  .createQuery();

简单查询字符串搜索

前面的查询方式都需要指定查询类型。使用简单查询字符串可以实现更强大的搜索功能,即实现运行时查询能力。其支持的查询类型如下:

  • 布尔类型(and使用+,or使用|,not使用-)
  • 前缀查询(前缀*)
  • 短语查询(“一些短语”)
  • 优先级(使用括号)
  • 近似查询(近似查询-2)
  • 短语查询的邻近运算符(“某些短语”-3)
下面示例综合了近似查询、词组查询以及布尔查询:
Query simpleQueryStringQuery = queryBuilder
  .simpleQueryString()
  .onFields("productName", "description")
  .matching("Aple~2 + \"iPhone X\" + (256 | 128)")
  .createQuery();

范围查询

最后一个查询类型是同类词查询(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();

同类词查询

最后一个查询类型是同类词查询(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();

基于多个字段查询

目前为止我们一直使用onField()方法在一个字段上执行查询,实际应用中也能基于多个字段:

Query luceneQuery = queryBuilder
  .keyword()
  .onFields("productName", "description")
  .matching(text)
  .createQuery();

而且,也可以针对每个字段分别搜索。如我们可以对不同字段定义不同的加权因子(boost):

Query moreLikeThisQuery = queryBuilder
  .moreLikeThis()
  .comparingField("productName").boostedTo(10f)
  .andField("description").boostedTo(1f)
  .toEntity(entity)
  .createQuery();

组合查询

最后Hibernate Search也支持使用不同策略实现组合查询:

SHOULD: 查询应该包括子查询匹配的元素
MUST: 查询必须包含子查询匹配的元素
MUST NOT: 查询不必包含子查询的元素
三者类似于布尔运算:and、or和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();
 类似资料: