O problema que você está experimentando tem a ver com a maneira como você está usando o
HINT_PASS_DISTINCT_THROUGH
dica. Esta dica permite que você indique ao Hibernate que o
DISTINCT
palavra-chave não deve ser usada no SELECT
declaração emitida contra o banco de dados. Você está aproveitando esse fato para permitir que suas consultas sejam classificadas por um campo que não está incluído no
DISTINCT
lista de colunas. Mas não é assim que essa dica deve ser usada.
Esta dica só deve ser usada quando você tiver certeza de que não haverá diferença entre aplicar ou não um
DISTINCT
palavra-chave para o SQL SELECT
instrução, porque o SELECT
já irá buscar todos os valores distintos per se . A ideia é melhorar o desempenho da consulta evitando o uso de um DISTINCT
desnecessário declaração. Isso geralmente acontece quando você usa o
query.distinct
método em suas consultas de critérios e você está join fetching
relacionamentos infantis. Este ótimo artigo
de @VladMihalcea explicam detalhadamente como a dica funciona. Por outro lado, quando você usa paginação, ele definirá
OFFSET
e LIMIT
- ou algo semelhante, dependendo do banco de dados subjacente - no SQL SELECT
declaração emitida contra o banco de dados, limitando a um número máximo de resultados sua consulta. Conforme indicado, se você usar o
HINT_PASS_DISTINCT_THROUGH
dica, o SELECT
declaração não conterá o DISTINCT
palavra-chave e, por causa de suas junções, poderia fornecer registros duplicados de sua entidade principal. Esses registros serão processados pelo Hibernate para diferenciar duplicatas, pois você está usando query.distinct
, e de fato removerá duplicatas, se necessário. Acho que esse é o motivo pelo qual você pode obter menos registros do que o solicitado em seu Pageable
. Se você remover a dica, como
DISTINCT
palavra-chave é passada na instrução SQL que é enviada para o banco de dados, na medida em que você projeta apenas informações da entidade principal, ela buscará todos os registros indicados por LIMIT
e é por isso que lhe dará sempre o número de registros solicitados. Você pode tentar
fetch join
suas entidades filhas (em vez de apenas join
com eles). Isso eliminará o problema de não poder usar o campo pelo qual você precisa classificar nas colunas do DISTINCT
palavra-chave e, além disso, você poderá aplicar, agora de forma legítima, a dica. Mas se você fizer isso, terá outro problema:se você usar join fetch e paginação, para retornar as entidades principais e suas coleções, o Hibernate não aplicará mais paginação no nível do banco de dados - não incluirá
OFFSET
ou LIMIT
palavras-chave na instrução SQL e tentará paginar os resultados na memória. Este é o famoso Hibernate HHH000104
aviso:HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
@VladMihalcea explica isso detalhadamente na última parte de este artigo.
Ele também propôs uma possível solução para o seu problema, Window Functions .
No seu caso de uso, em vez de usar
Specification
s, a ideia é que você implemente seu próprio DAO. Este DAO só precisa ter acesso ao EntityManager
, o que não é muito importante, pois você pode injetar seu @PersistenceContext
:@PersistenceContext
protected EntityManager em;
Depois de ter este
EntityManager
, você pode criar consultas nativas e usar funções de janela para construir, com base no Pageable
fornecido informações, a instrução SQL correta que será emitida no banco de dados. Isso lhe dará muito mais liberdade sobre quais campos usam para classificação ou o que você precisar. Como indica o último artigo citado, o Window Functions é um recurso suportado por todos os bancos de dados do prefeito.
No caso do PostgreSQL, você pode encontrá-los facilmente na documentação oficial .
Finalmente, mais uma opção, sugerida de fato por @nickshoe, e explicada detalhadamente no artigo ele citou, é realizar o processo de ordenação e paginação em duas fases:na primeira fase, você precisa criar uma consulta que fará referência às suas entidades filhas e na qual você aplicará paginação e ordenação. Esta consulta permitirá identificar os ids das principais entidades que serão utilizadas, na segunda fase do processo, para obter as próprias entidades principais.
Você pode aproveitar o DAO personalizado mencionado acima para realizar esse processo.