Hibernate 搜索分页奇怪的行为

Hibernate Search Pagination Strange Behaviour

您好,我们正在使用 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 个结果。

进一步研究发现问题出在hibernate-search中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 对象中 callable 被设置为 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"

如果能帮助解释为什么会发生这种情况以及如何正确实施分页,我们将不胜感激。

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

此行为的原因是 Elasticsearch "near real-time":在我们对索引进行更改后,更改将需要一些时间(通常是几秒钟)才能在搜索结果中可见。因此,如果您只是在几毫秒前删除了实体,则索引状态可能 "lag" 落后于数据库状态。

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

测试中的解决方案

如果这是测试时的问题,您可以将hibernate.search.default.elasticsearch.refresh_after_write设置为true你不应该在生产中设置这个,因为这会大大降低索引的性能。

生产中的解决方案

如果这是生产中的问题,需要高效解决,难度会更大。我能想到的唯一解决方案是从按索引分页改为按键分页。但是,您将无法直接转到某个页面,并且无法按您想要的方式对结果进行排序。

您需要在您的结果中找到一个严格单调键,即保证每个结果都是唯一的并且在以下情况下始终增加(或始终减少)的字段你去下一个结果。如果按 id 排序,id 将是一个很好的选择。创建日期也可以,如果它足够精确并且您按此创建日期排序。

您将使用此键忽略前面的页面:客户端不会将页码发送到服务器,它会发送 "strictly monotonic" 键的最后一个值,您只需添加一个谓词像这样查询:queryBuilder.range().onField("myKey").above(<the last value for the key in the previous page>).createQuery().

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

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

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

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