Hibernate 搜索前缀

Hibernate search on prefixes

现在,我已经成功配置了一个基本的 Hibernate 搜索索引,以便能够在我的 JPA 实体的各个字段上搜索完整的单词:

@Entity
@Indexed
class Talk {
    @Field String title
    @Field String summary
}

我的查询看起来像这样:

List<Talk> search(String text) {
    FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager)
    QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Talk).get()
    Query query = queryBuilder
            .keyword()
            .onFields("title", "summary")
            .matching(text)
            .createQuery()
    FullTextQuery jpaQuery = fullTextEntityManager.createFullTextQuery(query, Talk)
    return jpaQuery.getResultList()
}

现在我想 fine-tune 这个设置,这样当我搜索 "test" 时它仍然会找到标题或摘要包含 "test" 甚至作为另一个词的前缀的演讲。因此,标题为 "unit testing" 或摘要包含 "testicle" 的演讲仍应出现在搜索结果中,而不仅仅是标题或摘要包含 "test" 作为完整单词的演讲。

我试图查看文档,但我无法确定是否应该更改实体的索引方式,或者它是否与查询有关。请注意,我想执行如下操作,但很难在多个字段上进行搜索:

 Query query = queryBuilder
            .keyword().wildcard()
            .onField("title")
            .matching(text + "*")
            .createQuery()

编辑: 根据 Hardy 的回答,我这样配置了我的实体:

@Indexed
@Entity
@AnalyzerDefs([
@AnalyzerDef(name = "ngram",
        tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
        filters = [
            @TokenFilterDef(factory = LowerCaseFilterFactory.class),
            @TokenFilterDef(factory = NGramFilterFactory.class,
                    params = [
                        @Parameter(name = "minGramSize",value = "3"),
                        @Parameter(name = "maxGramSize",value = "3")
                    ])
        ])
])
class Talk {
    @Field(analyzer=@Analyzer(definition="ngram")) String title
    @Field(analyzer=@Analyzer(definition="ngram")) String summary
}

由于该配置,当我搜索 'arti' 时,我得到标题或摘要包含 'arti' 是(艺术家、工匠等)的子词的演讲。不幸的是,在这些之后,我还得到了标题或摘要包含包含我的搜索词的子词(艺术、放屁等)的词的演讲。可能有一些 fine-tuning 可以消除这些,但至少我现在可以更快地得到结果,而且它们的顺序合理。

您可以在这里做很多事情。通过在索引时间进行适当的分析,可以做很多事情。

例如,您想要应用适合您的语言的词干提取器。对于英语,这通常是 Snowball stemmer.The 的想法是在索引期间所有单词都减少到它们的词干,testingtested 到 _test例如。这会让您有所收获。

您可以研究的另一件事是 ngramm 索引。根据您的描述,您还想在不相关的词中找到匹配项。这里的想法是索引每个单词 "subwords",以便以后可以找到它们。

关于您想要查看 Hibernate Search 文档的 named analyzers 部分的分析器。这里的关键是 @AnalyzerDef 注释。

在查询端你也可以应用一些"tricks"。实际上,您可以使用通配符查询,但是,如果您使用的是 Hibernate Search 查询 DSL,则不能使用 keyword 查询,但需要使用 wildcard 查询。再次检查 Hibernate Search 文档。

在 Lucene 4.9 版中,我为此使用了 EnglishAnalyzer。我认为这是 SnowballAnalyzer 的纯英文实现,但不是 100% 确定。我用它来创建和搜索索引。没有什么特别需要使用它。

Analyzer analyzer = new EnglishAnalyzer(Version.LUCENE_4_9);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_4_9, analyzer);

analyzer = new EnglishAnalyzer(Version.LUCENE_4_9);
parser = new StandardQueryParser(analyzer);

您可以在 Guided Code Search 上看到它的实际效果。这仅在 Lucene 上运行。

Lucene 可以集成到 Hibernate 搜索中,但我自己还没有尝试这样做。好像很厉害,不知道:见Apache Lucene™ Integration.

我还读到可以将 lucene 修补到 SQL 引擎中,但我也没有尝试过。示例:Indexing Databases with Lucene

您应该使用 NgramEdgeNGram 过滤器作为索引输入,正如您在回答中正确指出的那样。但是您应该按照 lucene 文档中的建议对您的查询使用不同的分析器(参见 search_analyzer): https://www.elastic.co/guide/en/elasticsearch/guide/current/_index_time_search_as_you_type.html

这样您的搜索查询就不会被标记为 ngram,您的结果将更像是 SQL 中的 %text%text%

不幸的是,由于未知原因,Hibernate Search 目前不支持 search_analyzer 字段规范。您只能将特定的分析器用于索引,这也将用于搜索查询分析。

我打算自己实现这个功能。

编辑:

您可以像这样指定搜索时间分析器 (search_analyzer):

List<Talk> search(String text) {
    FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager)
    EntityContext entityContext = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Talk);

    entityContext.overridesForField("myField", "myNamedAnalyzerDef");

    QueryBuilder queryBuilder = ec.get()
    Query query = queryBuilder
            .keyword()
            .onFields("title", "summary")
            .matching(text)
            .createQuery()
    FullTextQuery jpaQuery = fullTextEntityManager.createFullTextQuery(query, Talk)
    return jpaQuery.getResultList()
}

我已经使用这种技术有效地模拟了 Lucene search_analyzer 属性.