具有自动完成功能和模糊功能的休眠搜索

时间:2020-04-16 13:46:13

标签: java hibernate lucene hibernate-search

我正在尝试创建StingUtils containsIgnoreCase()方法的休眠搜索表示形式以及模糊搜索匹配

假设用户写了字母“ p”,他们将获得包括字母“ p”的所有匹配项(无论该字母位于各个匹配项的开头,中间还是结尾)。

当它们形成诸如“ Peter”之类的单词时,它们也应获得模糊匹配,例如“ Petar”,“ Petaer”和“ Peder”。

我使用的是很好的答案here中提供的自定义查询和索引分析器,因为我需要将minGramSize设置为1才能使用自动完成功能,与此同时,我也希望用空格分隔的单词用户输入,例如“ Peter的EUR帐户”,在不同情况下(较低或较高)。

因此,用户应该能够键入“ AND”并接收上面的示例作为匹配项。

当前,我正在使用以下查询:

  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name")
                                                   .matching(userInput).createQuery();
  booleanQuery.add(fuzzySearchByName, BooleanClause.Occur.MUST);

但是,完全匹配的案例在搜索结果中不占优势:

如果键入“ petar”,则会得到以下结果:

  1. Petarr (不完全匹配)
  2. Petaer (不完全匹配)

... 4. PETAR 完全匹配

“ peter”的用户输入也是如此,第一个结果是“ Petero”,第二个结果是“ Peter”(第二个应该是第一个)。

我还需要在多字查询中仅包含完全匹配的内容,例如如果我开始写“ Account for ... ”,我希望所有匹配的结果都包括短语“ Account for ”,并最终基于该短语与模糊相关短语(基本上与之前显示的containsIgnoreCase()方法相同,只是尝试添加模糊支持)

但是我猜这与minGramSize为1和WhitespaceTokenizerFactory是矛盾的吗?

1 个答案:

答案 0 :(得分:1)

但是,完全匹配的案例在搜索结果中不占优势:

只需使用两个查询而不是一个:

编辑:您还需要为自动填充和“完全匹配”设置两个单独的字段;在底部查看我的修改。

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
  booleanQuery.add(searchByName, BooleanClause.Occur.MUST);

这将匹配包含大约用户输入的文档,因此,它将匹配与您的示例相同的文档。但是,包含用户输入的文档将完全匹配这两个查询,而仅包含相似内容的文档将仅匹配模糊查询。因此,完全匹配将获得更高的分数,并最终在结果列表中更高。

如果精确匹配还不够高,请尝试对exactSearchByName查询进行增强:

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput)
                                                   .boostedTo(4.0f)
                                                   .createQuery();

但是我想这与minGramSize为1和WhitespaceTokenizerFactory矛盾吗?

如果要匹配包含在用户输入中的任何单词(但不一定是所有单词)的文档,并将包含更多单词的文档放在结果列表的上方,请执行我上面的解释。

如果要匹配包含所有单词的文件,它们的顺序完全相同,请使用KeywordTokenizerFactory(即,不带标记)。

如果要匹配包含所有顺序的所有单词的文档,那么……不太明显。 Hibernate Search(yet)不支持此功能,因此,您基本上必须自己构建查询。我已经见过的一种骇客是这样的:

Analyzer analyzer = fullTextSession.getSearchFactory().getAnalyzer( "myAnalyzer" );

QueryParser queryParser = new QueryParser( "name", analyzer );
queryParser.setOperator( Operator.AND ); // Match *all* terms
Query luceneQuery = queryParser.parse( userInput );

...但是不会生成模糊查询。如果要进行模糊查询,则可以尝试覆盖QueryParser的自定义子类中的某些方法。我没有尝试过,但是可能有效:

public final class FuzzyQueryParser extends QueryParser {
    private final int maxEditDistance;
    private final int prefixLength;

    public FuzzyQueryBuilder(String fieldName, Analyzer analyzer, int maxEditDistance, int prefixLength) {
        super( fieldName, analyzer );
        this.maxEditDistance = maxEditDistance;
        this.prefixLength = prefixLength;
    }

    @Override
    protected Query newTermQuery(Term term) {
        return new FuzzyQuery( term, maxEditDistance, prefixLength );
    }
}

编辑:minGramSize为1时,您会得到很多非常常用的术语:从单词开头提取的一个或两个字符的术语。可能会导致许多不必要的比赛,这些比赛会被高分(因为该术语很常见),并且可能会淹没确切的比赛。

首先,您可以尝试将相似性(〜评分公式)设置为org.apache.lucene.search.similarities.BM25Similarity,这更适合于忽略非常频繁的用语。参见here for the setting。相同的分析仪可以提高得分。

第二,您可以尝试设置两个字段,而不是一个字段:一个字段用于模糊自动完成,而一个字段用于非模糊完全匹配。这将提高精确匹配的分数,因为对于精确匹配使用的字段索引的无意义术语会更少。只需这样做:

@Field(name = "name", analyzer = @Analyzer(definition = "text")
@Field(name = "name_autocomplete", analyzer = @Analyzer(definition = "edgeNgram")
private String name;

分析器“文本”只是来自answer you linked的分析器“ edgeNGram_query”;只需重命名即可。

继续编写两个查询,而不是如上所述的一个查询,但是请确保针对两个不同的字段:

  org.apache.lucene.search.Query exactSearchByName = qb.keyword().onField("name")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query fuzzySearchByName = qb.keyword().fuzzy()
                                                   .withEditDistanceUpTo(1).onField("name_autocomplete")
                                                   .matching(userInput).createQuery();
  org.apache.lucene.search.Query searchByName = qb.boolean().should(exactSearchByName).should(fuzzySearchByName).createQuery();
  booleanQuery.add(searchByName, BooleanClause.Occur.MUST);

当然不要忘记在这些更改后重新编制索引。