您正在尝试的问题与您使用的方式有关HINT_PASS_DISTINCT_THROUGH
hint.
此提示允许您指示 HibernateDISTINCT
关键字不应该用在SELECT
针对数据库发出的语句。
您正在利用这一事实来允许您的查询按未包含在DISTINCT
列列表。
但这不是应该如何使用此提示。
仅当您确定应用或不应用之间没有区别时才必须使用此提示DISTINCT
SQL 的关键字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 LIMIT
SQL语句中的关键字,它会尝试在内存中对结果进行分页。这就是著名的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/.
在您的用例中,而不是使用Specification
s,这个想法是你实现自己的 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 来完成此过程。