【问题标题】:Hibernate Full text Serch - order results by relevanceHibernate 全文搜索 - 按相关性排序结果
【发布时间】:2020-06-29 21:41:10
【问题描述】:

我正在尝试使用 Hibernate Search 版本 5.5.0.Final 进行全文查询(我已经尝试过使用最新版本,但由于我使用的是旧版本的 Hibernate(5.0 .12) )。

我想得到的最终结果如下:

Display at the top of the list the result that matches on the description field with the following logic:
    (Let' assume a user is searching "Milk")
    -Results having the word at the beginning (Milk UHT)
    -Results having the word in second or third position (Chocolate Milk)
    -Results having the word in a phrase(MilkShake)
Then displaying the result matching with the field tags (Lactose free, Gluten Free etc)

这是我到目前为止所做的:

FullTextEntityManager fullTextEntityManager
            = Search.getFullTextEntityManager(entityManager);
    fullTextEntityManager.createIndexer().startAndWait();


    FullTextEntityManager fullTextEntityManager2
            = Search.getFullTextEntityManager(entityManager);

    QueryBuilder queryBuilder = fullTextEntityManager2.getSearchFactory()
            .buildQueryBuilder()
            .forEntity(ProductEntity.class)
            .get();


    Query myQuery = queryBuilder
            .bool()
            .should(queryBuilder.keyword()
                    .onField("description").boostedTo(9l).matching(query)
                    .createQuery())
            .should(queryBuilder.phrase()
                    .onField("description").boostedTo(5l).sentence(query)
                    .createQuery())

            .should(queryBuilder.keyword()
                    .onField("tags").boostedTo(3l).matching(query)
                    .createQuery())
            .should(queryBuilder.phrase()
                    .onField("tags").boostedTo(1l).sentence(query)
                    .createQuery())

            .createQuery();


    org.hibernate.search.jpa.FullTextQuery jpaQuery
            = fullTextEntityManager.createFullTextQuery(myQuery, ProductEntity.class);

    return jpaQuery.getResultList();

我已经在互联网上阅读了很多内容,但仍然无法获得想要的结果。 这甚至可能吗?能给我一个提示吗?

提前致谢

【问题讨论】:

    标签: hibernate elasticsearch full-text-search hibernate-search


    【解决方案1】:

    首先,要知道提升不是分配给每个查询的恒定权重;相反,它是一个乘数。因此,当您在查询 #4 上将 boost 设置为 1 并在查询 #3 上设置为 3 时,如果查询 #4 的基本分数是查询 #3 的三倍以上,则理论上可能会以更高的“提升分数”结束.为避免此类问题,您可以将每个查询的分数标记为常量(使用.boostedTo(3l).withConstantScore().onField("tags") 而不是.onField("tags").boostedTo(3l)

    其次,词组查询不是你想的那样。短语查询接受多词条输入字符串,并会以相同顺序查找包含这些词条的文档。既然你过了一个学期,那就没有意义了。所以你需要别的东西。

    查询 1:以单词开头的结果

    我相信完全你想要的唯一方法是跨度查询。但是,它们不是 Hibernate Search DSL 的一部分,因此您必须依赖低级 Lucene API。更重要的是,我从来没有使用过它们,我不确定它们应该如何使用......我所知道的很少来自Elasticsearch's documentation,但是Lucene文档严重缺乏。

    你可以尝试这样的事情,但如果它不起作用,你必须自己调试它(我不知道比你多):

        QueryBuilder queryBuilder = fullTextEntityManager2.getSearchFactory()
                .buildQueryBuilder()
                .forEntity(ProductEntity.class)
                .get();
        Analyzer analyzer = fullTextEntityManager.getSearchFactory()
                .getAnalyzer(ProductEntity.class);
    
        Query myQuery = queryBuilder
                .bool()
                .should(new BoostQuery(new ConstantScoreQuery(createSpanQuery(qb, "description", query, analyzer)), 9L))
                [... add other clauses here...]
                .createQuery();
    
    // Other methods (to be added to the same class)
    
        private static Query createSpanQuery(QueryBuilder qb, String fieldName, String searchTerms, Analyzer analyzer) {
            BooleanJunction bool = qb.bool();
            List<String> terms = analyze(fieldName, searchTerms, analyzer);
           for (int i = 0; i < terms.size(); ++i) {
                bool.must(new SpanPositionRangeQuery(new SpanTermQuery(new Term( fieldName, terms.get(i))), i, i);
            }
            return bool.createQuery();
        }
    
        private static List<String> analyze(String fieldName, String searchTerms, Analyzer analyzer) {
            List<String> terms = new ArrayList<String>();
            try {
                final Reader reader = new StringReader( searchTerms );
                final TokenStream stream = analyzer.tokenStream( fieldName, reader );
                try {
                    CharTermAttribute attribute = stream.addAttribute( CharTermAttribute.class );
                    stream.reset();
                    while ( stream.incrementToken() ) {
                        if ( attribute.length() > 0 ) {
                            String term = new String( attribute.buffer(), 0, attribute.length() );
                            terms.add( term );
                        }
                    }
                    stream.end();
                }
                finally {
                    stream.close();
                }
            }
            catch (IOException e) {
                throw new IllegalStateException( "Unexpected exception while analyzing search terms", e );
            }
            return terms;
        }
    
    

    查询 2:单词在第二或第三位置的结果

    我相信您可以使用与查询 1 相同的代码,但添加一个偏移量。如果实际位置无关紧要,并且您会接受第四或第五位置的单词,您可以简单地这样做:

    queryBuilder.keyword().boostedTo(5l).withConstantScore()
            .onField("description").matching(query)
           .createQuery()
    

    查询 3:在短语中包含单词的结果(MilkShake)

    据我了解,您的意思是“包含包含搜索词的单词的结果”。

    您可以为此使用通配符查询,但不幸的是,这些查询不应用分析器,从而导致区分大小写的搜索(以及其他问题)。

    您最好的选择可能是为此查询定义一个单独的字段,例如description_ngram,并为其分配一个特制的分析器,它在索引时使用 ngram 标记器。 ngram 分词器只接受一个输入字符串并将其转换为它的所有子字符串:“milkshake”将变为["m", "mi", "mil", "milk", ..., "milkshake", "i", "il", "ilk", "ilks", "ilksh", ... "ilkshake", "l", ... "lkshake", ..., "ke", "e"]。显然它需要大量磁盘空间,但它可以用于小型数据集。 您将找到类似用例 here 的说明。答案提到了一个不同的分析器,“edgengram”,但在你的情况下,你真的想使用“ngram”分析器。

    或者,如果您确定索引文本的格式正确,可以清楚地分隔“复合”词的组成部分(例如“milk-shake”、“MilkShake”...),您可以简单地创建一个字段 (例如description_worddelimiterfilter),它使用带有单词分隔符过滤器的分析器(请参阅org.apache.lucene.analysis.miscellaneous.WordDelimiterFilter),它将拆分这些复合词。然后你可以像这样简单地查询:

    queryBuilder.keyword().boostedTo(3l).withConstantScore()
            .onField("description_worddelimiterfilter")
            .matching(query)
            .createQuery()
    

    【讨论】:

    • 您好,感谢您的回复。我认为实现我的想法更容易。毕竟这是一个基本的搜索。我正在尝试实施您的建议。
    • 我有以下错误: Query myQuery = queryBuilder .bool() .should(new BoostQuery(new ConstantScoreQuery(createSpanQuery(queryBuilder, "description", query, analyzer)), 9L));必需:org.apache.lucene.search.Query 找到:org.hibernate.search.query.dsl.BooleanJunction /**************************** ****************************************************** ******/ final PagedBytes.Reader reader = new StringReader(searchTerms);必需:org.apache.lucene.util.PagedBytes.Reader 找到:java.io.StringReader
    • 您需要添加其他子句并以.createQuery() 结束语句。我编辑了我的答案以澄清这一点。
    • 此外,虽然这是一个相对基本的搜索,但您也在尝试做一些非常不寻常的事情。如果可以的话,我建议您挑战您的要求并尝试为您的搜索查询找到一个不那么奇特的定义。特别是关于要求术语的特定位置的位是非常不寻常的(我从未见过这种要求)。
    • 我唯一想做的就是获得相关性结果...如果我搜索“橄榄油”,我希望在“橄榄油金枪鱼”之前出现一个橄榄油瓶,或“ Pasta BrandName ”该品牌的所有文章都在结果的顶部......目前这没有出现
    猜你喜欢
    • 2012-11-09
    • 2010-11-04
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 2014-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多