提示 HINT_PASS_DISTINCT_THROUGH 将 PageRequest 每页返回的实体数量减少到配置的页面大小以下 (PostgreSQL)

2024-01-02

我正在设置一个基于 JPA 规范的存储库实现,该实现利用 jpa 规范(基于 RSQL 过滤器字符串构建)来过滤结果、定义结果排序并通过“不同”删除任何重复项,否则这些重复项将因连接表而返回。 JPA 规范构建器方法连接多个表并设置“不同”标志:

final Join<Object, Object> rootJoinedTags = root.join("tags", JoinType.LEFT);
final Join<Object, Object> rootJoinedLocations = root.join("location", JoinType.LEFT);
...
query.distinct(true);

为了允许按连接表列排序,我已将“HINT_PASS_DISTINCT_THROUGH”提示应​​用于相关存储库方法(否则,按连接表列排序会返回类似“排序列必须包含在 SELECT DISTINCT 查询中”的错误) 。

@QueryHints(value = {
        @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")
    })
    Page<SomeEntity> findAll(@Nullable Specification<SomeEntity> spec, Pageable pageable);

所述存储库方法的参数构造如下:

final Sort sort = getSort(searchFilter);
        final Specification spec = getSpecificationIfPresent(searchFilter);
        final PageRequest pageRequest = PageRequest.of(searchFilter.getPageNumber(), searchFilter.getLimit(), sort);

        return eventRepository.findAll(spec, pageRequest);

经过这些更改后,过滤和排序似乎按预期工作。但是,该提示似乎会导致在构建结果页面后应用“不同”过滤,从而将页面中返回的实体数量从配置的“大小”PageRequest 参数减少到过滤重复项后留下的任何内容出去。例如,如果我们使用“page=0”和“pageSize=10”创建一个 PageRequest,那么生成的 Page 可能仅返回 5 个“SomeEntity”实例,尽管数据库包含更多条目(确切地说是 177 个实体)这个案例)。如果我删除提示,则返回的实体编号再次正确。

问题:有没有办法使相同的规范查询设置与正确大小的页面一起工作(可能会添加一些其他提示以在构造页面对象之前执行重复过滤)?如果没有,那么我是否可以使用另一种方法来实现所需的基于规范的过滤,以及与“不同”一样的连接列排序和重复删除?

PS:PostgreSQL 是相关应用程序背后的数据库


您正在尝试的问题与您使用的方式有关HINT_PASS_DISTINCT_THROUGH hint.

此提示允许您指示 HibernateDISTINCT关键字不应该用在SELECT针对数据库发出的语句。

您正在利用这一事实来允许您的查询按未包含在DISTINCT列列表。

但这不是应该如何使用此提示。

仅当您确定应用或不应用之间没有区别时才必须使用此提示DISTINCTSQL 的关键字SELECT声明,因为SELECT语句已经将获取所有不同的值per se。这个想法是提高查询的性能,避免使用不必要的DISTINCT陈述。

当您使用query.distinct方法在你的条件查询中,你是join fetching儿童关系。这篇很棒的文章 https://planet.jboss.org/post/the_distinct_pass_through_hibernate_query_hint@VladMihalcea 的详细解释了该提示的工作原理。

另一方面,当您使用分页时,它会设置OFFSET and LIMIT- 或类似的东西,取决于底层数据库 - 在 SQL 中SELECT针对数据库发出的语句,限制查询的最大结果数。

如前所述,如果您使用HINT_PASS_DISTINCT_THROUGH提示,该SELECT声明将不包含DISTINCT关键字,并且由于您的联接,它可能会提供您的主要实体的重复记录。该记录将由 Hibernate 处理以区分重复项,因为您正在使用query.distinct,如果需要的话,它实际上会删除重复项。我认为这就是为什么您获得的记录可能少于您的要求的原因Pageable.

如果您删除提示,如DISTINCT发送到数据库的SQL语句中传入关键字,只要你只投影主实体的信息,它就会获取所有由LIMIT这就是为什么它总是会为您提供所需数量的记录。

你可以尝试fetch join您的子实体(而不是仅join跟他们)。它将消除无法使用您需要在列中排序的字段的问题DISTINCT关键字,此外,您现在将能够合法地应用提示。

但如果你这样做,就会出现另一个问题:如果你使用 join fetch 和分页来返回主要实体及其集合,Hibernate 将不再在数据库级别应用分页 - 它不会包含OFFSET or LIMITSQL语句中的关键字,它会尝试在内存中对结果进行分页。这就是著名的HibernateHHH000104警告:

HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

@VladMihalcea 在最后一部分详细解释了这一点this https://vladmihalcea.com/query-pagination-jpa-hibernate/文章。

他还针对您的问题提出了一种可能的解决方案,窗口函数 https://vladmihalcea.com/why-you-should-definitely-learn-sql-window-functions/.

在您的用例中,而不是使用Specifications,这个想法是你实现自己的 DAO。该 DAO 只需要访问EntityManager,这并不是什么大不了的事情,因为你可以注入你的@PersistenceContext:

@PersistenceContext
protected EntityManager em;

一旦你有了这个EntityManager,您可以创建本机查询并使用窗口函数来构建,基于提供的Pageable信息,将针对数据库发出的正确 SQL 语句。这将为您提供更多自由来决定使用哪些字段进行排序或您需要的任何内容。

正如最后引用的文章所示,窗口函数是所有市长数据库都支持的功能。

对于 PostgreSQL,您可以轻松地在官方文档 https://www.postgresql.org/docs/9.1/tutorial-window.html.

最后,还有一个选项,实际上是由 @nickshoe 建议的,并在article https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/他引用的方法是分两个阶段执行排序和分页过程:在第一阶段,您需要创建一个查询来引用您的子实体,并在其中应用分页和排序。此查询将允许您识别将在流程的第二阶段中使用的主要实体的 ID,以获取主要实体本身。

您可以利用前面提到的自定义 DAO 来完成此过程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

提示 HINT_PASS_DISTINCT_THROUGH 将 PageRequest 每页返回的实体数量减少到配置的页面大小以下 (PostgreSQL) 的相关文章

随机推荐