休眠搜索分页奇怪的行为

时间:2018-12-18 17:47:31

标签: hibernate elasticsearch hibernate-search

嗨,我们正在将hibernate-search和elasticsearch一起使用。

索引工作正常,但是在分页结果时会看到奇怪的行为。

org.hibernate.Query hibQuery =
            fullTextSession.createFullTextQuery(query, 
Person.class).setFirstResult(0).setMaxResults(10);

return hibQuery.list();

如果忽略setFirstResult(0).setMaxResults(10),我们将获得700个结果,但同时设置了两个参数,我们将获得0个结果。

进一步的研究表明,问题出在休眠搜索的QueryLoader的这段代码中

objectInitializer.initializeObjects(
            entityInfos,
            idToObjectMap,
            new ObjectInitializationContext( criteria, entityType, extendedIntegrator, timeoutManager, session )
    );

ArrayList<Object> result = new ArrayList<>( idToObjectMap.size() );
    for ( Object o : idToObjectMap.values() ) {
        if ( o != ObjectInitializer.ENTITY_NOT_YET_INITIALIZED ) {
            result.add( o );
        }
    }
    return result;

在上面的代码行中

 if ( o != ObjectInitializer.ENTITY_NOT_YET_INITIALIZED )

所有idToObjectMap条目都返回false

进一步的研究表明,hibernate可以构建查询,并且sql看起来正确,但是在QueryParanters对象中,可调用对象设置为false,并且从不执​​行查询。

相关库

compile "org.hibernate:hibernate-core:5.9.2.Final"
compile "org.hibernate:hibernate-search-orm:5.9.2.Final"
compile "org.hibernate:hibernate-search-elasticsearch:5.9.2.Final"

任何帮助您解释为什么会发生这种情况以及如何正确实现分页的方法将不胜感激。

1 个答案:

答案 0 :(得分:1)

通常,当实体存在于索引中而不是数据库中时(通常),就会发生这种情况。对于您而言,前10个结果似乎在您的索引中,但不在数据库中。

此行为的原因是Elasticsearch是“近实时”的:我们对索引进行更改后,更改将需要一些时间(通常为几秒钟),直到它们在搜索结果中可见。因此,如果您只是在几毫秒前删除了实体,则索引状态可能会“落后”数据库状态。

如果您确定实体仍存在于数据库中,则ID映射或您选择的特定查询配置可能存在问题。如果不使用默认值,请向我们显示Person类的代码,并为您提供设置为属性hibernate.search.query.object_lookup_methodhibernate.search.query.database_retrieval_method的值。

测试解决方案

如果在测试时出现问题,可以将hibernate.search.default.elasticsearch.refresh_after_write设置为true您不应在生产环境中设置此项,因为这会大大降低索引编制的性能。

生产解决方案

如果这是生产中的问题,并且您需要有效解决,那么将更加困难。我能想到的唯一解决方案是从按索引的分页转移到按键的分页。但是,您将失去直接进入页面的能力,也将无法以任何方式对结果进行排序。

您将需要在结果中找到一个严格单调键,即保证每个结果唯一的字段,并在转到下一个字段时始终增加(或始终减少)结果。如果您按ID排序,则ID将是一个不错的选择。如果创建日期足够精确,并且您可以按此创建日期进行排序,那么它也可以使用。

您将使用此键忽略先前的页面:客户端不会将页码发送到服务器,它将发送“严格单调”键的最后一个值,而您只需要向其中添加谓词即可您的查询:queryBuilder.range().onField("myKey").above(<the last value for the key in the previous page>).createQuery()

然后,您将直接执行多次查询,而不是直接返回查询结果,而是将结果累积在列表中,直到达到合适的页面大小(或直到getResultSize返回0)为止。

编辑:另一个解决方案,也许更简单,但这只会减少此问题的可能性,而不能完全消除。

您可以通过将index.refresh_interval设置为比所有索引的默认值(1s短的值来确保Elasticsearch刷新索引的频率更高。请注意,这可能会对您的Elasticsearch集群的性能产生非常严重的影响,具体取决于您写入集群的频率。

为了将设置应用于所有索引,最简单的解决方案是在Hibernate Search创建索引之前创建index templates