在休眠搜索中搜索内部子字符串

时间:2019-06-13 16:16:13

标签: hibernate-search

我的实体定义如下。

@Entity
@Indexed
@AnalyzerDef(name = "ngram_index", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
    filters = {
        @TokenFilterDef(factory = LowerCaseFilterFactory.class),
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
        @TokenFilterDef(factory = NGramFilterFactory.class,
                        params = {
                            @Parameter(name = SearchConstants.MIN_GRAM_SIZE_NAME, value = SearchConstants.MIN_GRAM_SIZE_VALUE),
                            @Parameter(name = SearchConstants.MAX_GRAM_SIZE_NAME, value = SearchConstants.MAX_GRAM_SIZE_VALUE)
                        })
    })
@AnalyzerDef(name = "ngram_query", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
    filters = {
        @TokenFilterDef(factory = LowerCaseFilterFactory.class),
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
    })
@NormalizerDef(name = "lowercase",
    filters = {
        @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
        @TokenFilterDef(factory = LowerCaseFilterFactory.class)
    }
)

@Table(name = "ORDER")
public class Order {
    @Id
    @DocumentId
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

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

    //other fields, getters and setters omitted for brevity

然后,我尝试覆盖在索引中用于查询不是实体的另一个类中的默认分析器。

public abstract class AbstractHibernateSearcher<S extends SearchableEntity> {
    // other fields and methods omitted here 

    protected Query buildInputSearchQuery(String[] searchableFields) {
        if(Strings.isNullOrEmpty(searchRequest.getQuery()) || searchableFields.length == 0) {
            return null;
        }
        SimpleQueryStringMatchingContext simpleQueryStringMatchingContext = queryBuilder.simpleQueryString().onField(searchableFields[0]);
        for(int i = 1; i < searchableFields.length; i++) {
            simpleQueryStringMatchingContext = simpleQueryStringMatchingContext.andField(searchableFields[i]);
        }
        Query inputSearchQuery = simpleQueryStringMatchingContext
            .withAndAsDefaultOperator()
            .matching((searchRequest.getQuery()).toLowerCase()).createQuery();

        QueryBuilder queryBuilder = getNGramQueryBuilder(searchableFields);
        return queryBuilder.bool().must(inputSearchQuery).createQuery();
    }

    protected QueryBuilder getNGramQueryBuilder(String[] searchFields) {
        if (searchFields.length == 0) {
            return null;
        }
        EntityContext entityContext = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(clazz);
        for(String field : searchFields) {
            entityContext = entityContext.overridesForField(field, "ngram_query");
        }
        return entityContext.get();
    }
}

当我执行查询搜索时,这给了我以下错误。

{消息:“ HSEARCH000353:未知分析器:'ngram_query'。请确保已定义此分析器。”,…} 异常:“ RuntimeException” 消息:“ HSEARCH000353:未知分析器:'ngram_query'。请确保已定义此分析器。”

我从官方文档中发现了这一点。

您可以在任何以下位置使用@AnalyzerDef:

@带索引的实体,无论将分析器应用于何处;

@Indexed实体的父类;

包含@Indexed实体的软件包的

package-info.java。

由于我看到了未知的分析器,所以我想尝试用“ ngram_query”分析器覆盖的类对此分析器没有可见性吗?

1 个答案:

答案 0 :(得分:1)

是的,您可以为每个单词创建ngram:将WhitespaceTokenizerFactory用作令牌生成器,并将NGramFilterFactory添加到令牌过滤器中(请注意,它与您提到的类别不同:它是令牌过滤器,不是令牌生成器。

在查询时,您还需要使用其他分析器,该分析器不会创建ngram。否则,例如,键入“ manhantan”的用户可能会获得包含“ man”的文档的匹配项。 有关如何执行此操作的信息,请参见https://stackoverflow.com/a/56107399/6692043

请注意,ngram可以导致非常大的索引,尤其是如果您对“ minGramSize”和“ maxGramSize”参数的值不小心的话。

另一种解决方案是使用原始分析器和通配符查询,但是不幸的是,它忽略了分析,并且在使用前导通配符时可能会很慢(这是您在这里需要的)。

    protected Query inputFilterBuilder() {
        String[] searchableFields = getSearchableFields();
        if(searchableFields.length == 0) {
            return null;
        }
        TermMatchingContext termMatchingContext = queryBuilder.keyword().wildcard().onField(searchableFields[0]);
        for(int i = 1; i < searchableFields.length; i++) {
            termMatchingContext = termMatchingContext.andField(searchableFields[i]);
        }
        return termMatchingContext
            .matching(("*" + searchRequest.getQuery() + "*").toLowerCase()).createQuery();
    }

请注意,上面的代码仅在存在单个搜索字词时才有效。 searchRequest.getQuery()中一旦有空格,您将不会获得任何结果。但是,如果我理解正确的话,索引文本中可能会有空格,这正是您想要的。