Il problema che stai sperimentando ha a che fare con il modo in cui stai usando HINT_PASS_DISTINCT_THROUGH
suggerimento.
Questo suggerimento ti permette di indicare a Hibernate che il DISTINCT
la parola chiave non deve essere utilizzata in SELECT
dichiarazione rilasciata contro il database.
Stai sfruttando questo fatto per consentire alle tue query di essere ordinate in base a un campo che non è incluso in DISTINCT
elenco di colonne.
Ma non è così che dovrebbe essere usato questo suggerimento.
Questo suggerimento deve essere utilizzato solo quando sei sicuro che non ci sarà alcuna differenza tra l'applicazione o meno di un DISTINCT
parola chiave all'SQL SELECT
istruzione, perché SELECT
istruzione recupererà già tutti i valori distinti di per sé . L'idea è di migliorare le prestazioni della query evitando l'uso di un DISTINCT
non necessario dichiarazione.
Di solito è ciò che accadrà quando usi query.distinct
metodo nelle tue query sui criteri e sei join fetching
relazioni infantili. Questo fantastico articolo
di @VladMihalcea spiega in dettaglio come funziona il suggerimento.
D'altra parte, quando usi il paging, imposterà OFFSET
e LIMIT
- o qualcosa di simile, a seconda del database sottostante - in SQL SELECT
dichiarazione rilasciata contro il database, limitando a un numero massimo di risultati la tua query.
Come detto, se usi il HINT_PASS_DISTINCT_THROUGH
suggerimento, il SELECT
l'istruzione non conterrà il DISTINCT
parola chiave e, a causa dei tuoi join, potrebbe potenzialmente fornire record duplicati della tua entità principale. Questi record verranno elaborati da Hibernate per differenziare i duplicati, poiché stai utilizzando query.distinct
, e in effetti rimuoverà i duplicati se necessario. Penso che questo sia il motivo per cui potresti ottenere meno record di quanto richiesto nel tuo Pageable
.
Se rimuovi il suggerimento, come DISTINCT
viene passata nell'istruzione SQL che viene inviata al database, per quanto si proiettino solo le informazioni dell'entità principale, recupererà tutti i record indicati da LIMIT
ed è per questo che ti darà sempre il numero di record richiesto.
Puoi provare a fetch join
le tue entità figlio (anziché solo join
con loro). Eliminerà il problema di non poter utilizzare il campo che devi ordinare nelle colonne del DISTINCT
parola chiave e, inoltre, potrai applicare, ora legittimamente, il suggerimento.
Ma se lo fai, avrai un altro problema:se usi join fetch e impaginazione, per restituire le entità principali e le sue raccolte, Hibernate non applicherà più l'impaginazione a livello di database - non includerà OFFSET
o LIMIT
parole chiave nell'istruzione SQL e proverà a impaginare i risultati in memoria. Questo è il famoso HHH000104
di Hibernate avviso:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
@VladMihalcea lo spiega in dettaglio nell'ultima parte di questo articolo.
Ha anche proposto una possibile soluzione al tuo problema, Funzioni finestra .
Nel tuo caso d'uso, invece di usare Specification
s, l'idea è di implementare il proprio DAO. Questo DAO deve solo avere accesso a EntityManager
, il che non è un granché dato che puoi inserire il tuo @PersistenceContext
:
@PersistenceContext
protected EntityManager em;
Una volta ottenuto questo EntityManager
, puoi creare query native e utilizzare le funzioni della finestra per creare, in base al Pageable
fornito informazioni, l'istruzione SQL corretta che verrà emessa rispetto al database. Questo ti darà molta più libertà su quali campi utilizzare per l'ordinamento o qualunque cosa ti serva.
Come indica l'ultimo articolo citato, Window Functions è una funzionalità supportata da tutti i principali database.
Nel caso di PostgreSQL, puoi trovarli facilmente nella documentazione ufficiale .
Infine, un'altra opzione, suggerita appunto da @nickshoe, e spiegata in dettaglio nel articolo ha citato, consiste nell'eseguire il processo di ordinamento e impaginazione in due fasi:nella prima fase, è necessario creare una query che farà riferimento alle entità figlio e in cui applicherai il paging e l'ordinamento. Questa query ti consentirà di identificare gli ID delle entità principali che verranno utilizzate, nella seconda fase del processo, per ottenere le entità principali stesse.
Puoi sfruttare il summenzionato DAO personalizzato per eseguire questo processo.