如何同时支持标记化和非标记化搜索

时间:2019-03-26 23:51:27

标签: hibernate-search

我尝试进行休眠搜索以支持标记化和非标记化搜索(如果我在此处使用错误的术语,请原谅我)。一个例子如下。

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

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

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

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

当我仅使用整个单词进行搜索时,此设置可以正常工作。例如,如果我有两个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.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();
            }

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

{{1}}

后续问题 为什么上述调整实际上有效?

1 个答案:

答案 0 :(得分:1)

您的问题是通配符查询。通配符查询不支持令牌化:它们仅适用于单个令牌。实际上,他们甚至不支持标准化,这就是为什么您必须小写用户自己输入的原因...

解决方案不是将标记化搜索和未标记化搜索混合在一起(这是可能的,但并不能真正解决您的问题)。解决方案是完全忽略通配符查询,并在分析器中使用Edgengram过滤器。

有关详细说明,请参见this answer

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