【问题标题】:How to support tokenized and untokenized search at the same time如何同时支持分词和非分词搜索
【发布时间】:2019-03-26 23:51:26
【问题描述】:

我尝试让休眠搜索同时支持标记化和非标记化搜索(如果我在这里使用了错误的术语,请原谅我)。一个例子如下。

我有以下类型的实体列表。

@Entity
@Indexed
@NormalizerDef(name = "lowercase",
    filters = {
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
        @TokenFilterDef(factory = LowerCaseFilterFactory.class)
    }
)
public class Deal {
    //other fields omitted for brevity purposes

    @Field(store = Store.YES)
    @Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
    @SortableField(forField = "name_Sort")
    @Column(name = "NAME")
    private String name = "New Deal";

    //Getters/Setters omitted here
}

我还使用关键字方法来构建查询构建器,如下所示。 getSearchableFields 方法返回可搜索字段的列表。在此示例中,“名称”将在此返回列表中,因为 Deal 中的字段名称是可搜索的。

    protected Query inputFilterBuilder() {
        return queryBuilder.keyword()
            .wildcard().onFields(getSearchableFields())
            .matching("*" + searchRequest.getQuery().toLowerCase() + "*").createQuery();
    }

当我只使用整个单词进行搜索时,此设置可以正常工作。例如,如果我有两个 Deal 实体,一个名为“Practical Concrete Hat”,另一个名为“Practical Cotton Cheese”。通过“实用”搜索时,我得到了这两个实体。但是当通过“Practical Co”搜索时,我得到了 0 个实体。原因是字段名称被标记化了,“Practical Co”不是关键词。

我的问题是如何同时支持这两个搜索,以便如果通过“Practical”或“Practical Co”搜索,则返回这两个实体。

我阅读了官方的休眠搜索文档,我的直觉是我应该再添加一个用于未标记搜索的字段。也许我构建查询构建器的方式也需要更新?

更新

使用 SimpleQueryString 的解决方案无效。

根据提供的答案,我编写了以下查询构建器逻辑。但是,它不起作用。

    protected Query inputFilterBuilder() {
        String[] searchableFields = getSearchableFields();
        if(searchableFields.length == 0) {
            return queryBuilder.simpleQueryString().onField("").matching("").createQuery();
        }
        SimpleQueryStringMatchingContext simpleQueryStringMatchingContext = queryBuilder.simpleQueryString().onField(searchableFields[0]);
        for(int i = 1; i < searchableFields.length; i++) {
            simpleQueryStringMatchingContext = simpleQueryStringMatchingContext.andField(searchableFields[i]);
        }
        return simpleQueryStringMatchingContext
            .matching("\"" + searchRequest.getQuery() + "\"").createQuery();
    }

使用单独的分析器进行查询和短语查询的工作解决方案。

我从官方文档中发现,我们可以使用词组查询来搜索多个单词。所以我写了下面的查询构建器方法。

    protected Query inputFilterBuilder() {
        String[] searchableFields = getSearchableFields();
        if(searchableFields.length == 0) {
            return queryBuilder.phrase().onField("").sentence("").createQuery();
        }
        PhraseMatchingContext phraseMatchingContext = queryBuilder.phrase().onField(searchableFields[0]);
        for(int i = 1; i < searchableFields.length; i++) {
            phraseMatchingContext = phraseMatchingContext.andField(searchableFields[i]);
        }
        return phraseMatchingContext.sentence(searchRequest.getQuery()).createQuery();
    }

这不适用于使用多个单词且中间有空格的搜索。然后我按照建议添加了用于索引和查询的单独分析器,突然之间,它起作用了。

分析器定义:

@AnalyzerDef(name = "edgeNgram", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
    filters = {
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
        @TokenFilterDef(factory = LowerCaseFilterFactory.class),
        @TokenFilterDef(factory = EdgeNGramFilterFactory.class,
                        params = {
                            @Parameter(name = "minGramSize", value = "1"),
                            @Parameter(name = "maxGramSize", value = "10")
                        })
    })
@AnalyzerDef(name = "edgeNGram_query", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
    filters = {
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
        @TokenFilterDef(factory = LowerCaseFilterFactory.class)
    })

交易名称字段注释:

    @Field(store = Store.YES, analyzer = @Analyzer(definition = "edgeNgram"))
    @Field(name = "edgeNGram_query", store = Store.YES, analyzer = @Analyzer(definition = "edgeNGram_query"))
    @Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
    @SortableField(forField = "name_Sort")
    @Column(name = "NAME")
    private String name = "New Deal";

覆盖名称字段分析器以使用查询分析器的代码

            String[] searchableFields = getSearchableFields();
            if(searchableFields.length > 0) {
                EntityContext entityContext = fullTextEntityManager.getSearchFactory()
                    .buildQueryBuilder().forEntity(this.getClass().getAnnotation(SearchType.class).clazz()).overridesForField(searchableFields[0], "edgeNGram_query");

                for(int i = 1; i < searchableFields.length; i++) {
                    entityContext.overridesForField(searchableFields[i], "edgeNGram_query");
                }
                queryBuilder = entityContext.get();
            }

跟进问题 为什么上述调整实际上有效?

【问题讨论】:

    标签: hibernate-search


    【解决方案1】:

    您的问题是通配符查询。通配符查询不支持标记化:它们仅适用于单个标记。事实上,它们甚至不支持规范化,这就是为什么您必须自己将用户输入小写...

    解决方案不是混合标记化和非标记化搜索(这是可能的,但不会真正解决您的问题)。解决方案是完全忘记通配符查询,并在分析器中使用边缘图过滤器。

    请参阅this answer 了解详细说明。

    如果您使用 ELasticsearch 集成,您将不得不依靠 hack 来使“仅查询”分析器正常工作。见here

    【讨论】:

    • 感谢您的回复!我已阅读您答案中的相关链接,并注意到您在此链接中关于使用 SimpleQueryString 的评论。 stackoverflow.com/questions/43044350/…我用例中的实体也会有多个可搜索字段,那么我可以使用 SimpleQueryString 避免在每个可搜索字段编写自定义分析器注释吗?
    • 我重新编写了问题帖子中显示的查询构建器逻辑。通过添加额外的双引号,我使用 SimpleQueryParser 的短语运算符,以便查询将返回包含任何可搜索字段中的查询短语的结果。但是,它没有按预期工作,使用带有空格的短语进行搜索仍然没有返回任何内容。是因为这些可搜索字段的索引方式吗?
    • 评论部分的字符数限制很小,所以我在我的问题帖子中添加了一些更新。
    • 短语查询在文档中查找准确的单词序列,它们可能不是您要查找的。据我了解,我的建议对您有用;如果您还有其他问题,请提出其他问题,并详细说明您所说的“不起作用”是什么意思(异常?没有结果?错误的结果?如果有,预期结果是什么,您得到了什么?)
    • 啊,好的,我会问一个关于这个的新问题,谢谢!
    猜你喜欢
    • 2012-04-12
    • 1970-01-01
    • 2017-08-07
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多