Problém, který experimentujete, souvisí se způsobem, jakým používáte HINT_PASS_DISTINCT_THROUGH nápověda.
Tato nápověda vám umožňuje označit Hibernaci jako DISTINCT klíčové slovo by nemělo být použito v SELECT výpis vydaný proti databázi.
Využíváte této skutečnosti k tomu, abyste umožnili třídění vašich dotazů podle pole, které není zahrnuto v DISTINCT seznam sloupců.
Ale takhle by se tato nápověda neměla používat.
Tuto nápovědu je třeba použít pouze v případě, že jste si jisti, že nebude žádný rozdíl mezi použitím nebo nepoužitím DISTINCT klíčového slova do SQL SELECT příkaz, protože SELECT příkaz již načte všechny odlišné hodnoty per se . Cílem je zlepšit výkon dotazu a vyhnout se použití zbytečného DISTINCT prohlášení.
To se obvykle stane, když použijete query.distinct metoda ve vašich dotazech na kritéria a join fetching dětské vztahy. Tento skvělý článek
@VladMihalcea podrobně vysvětlí, jak nápověda funguje.
Na druhou stranu, když používáte stránkování, nastaví OFFSET a LIMIT - nebo něco podobného, v závislosti na podkladové databázi - v SQL SELECT prohlášení vydané proti databázi s omezením na maximální počet výsledků vašeho dotazu.
Jak je uvedeno, pokud použijete HINT_PASS_DISTINCT_THROUGH nápověda, SELECT příkaz nebude obsahovat DISTINCT klíčové slovo a kvůli vašim spojením může potenciálně poskytnout duplicitní záznamy vaší hlavní entity. Tyto záznamy budou zpracovány Hibernate za účelem rozlišení duplikátů, protože používáte query.distinct a v případě potřeby ve skutečnosti odstraní duplikáty. Myslím, že to je důvod, proč můžete získat méně záznamů, než je požadováno ve vaší Pageable .
Pokud nápovědu odstraníte, jako DISTINCT klíčové slovo je předáno v příkazu SQL, který je odeslán do databáze, pokud projektujete pouze informace o hlavní entitě, načte se všechny záznamy označené LIMIT a to je důvod, proč vám vždy poskytne požadovaný počet záznamů.
Můžete zkusit fetch join vaše podřízené entity (místo pouze join s nimi). Odstraní to problém s nemožností použít pole, podle kterého potřebujete seřadit ve sloupcích DISTINCT klíčové slovo a navíc budete moci použít, nyní legitimně, nápovědu.
Ale pokud tak učiníte, budete mít další problém:pokud použijete spojení načítání a stránkování k vrácení hlavních entit a jejich kolekcí, Hibernate již nebude používat stránkování na úrovni databáze – nebude zahrnovat OFFSET nebo LIMIT klíčová slova v příkazu SQL a pokusí se stránkovat výsledky v paměti. Toto je slavný Hibernate HHH000104 varování:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
@VladMihalcea to velmi podrobně vysvětlil v poslední části toto článek.
Navrhl také jedno možné řešení vašeho problému, Funkce oken .
Ve vašem případě použití namísto použití Specification s, myšlenka je, že implementujete své vlastní DAO. Tento DAO potřebuje pouze přístup k EntityManager , což není moc, protože můžete vložit svůj @PersistenceContext :
@PersistenceContext
protected EntityManager em;
Jakmile budete mít tento EntityManager , můžete vytvářet nativní dotazy a používat funkce okna k sestavení na základě poskytnutého Pageable informace, správný SQL příkaz, který bude vydán vůči databázi. To vám dá mnohem větší svobodu ohledně toho, jaká pole se používají k třídění nebo co potřebujete.
Jak naznačuje poslední citovaný článek, funkce okna je funkce podporovaná všemi databázemi starostů.
V případě PostgreSQL na ně můžete snadno narazit v oficiální dokumentaci .
Nakonec ještě jedna možnost, kterou ve skutečnosti navrhl @nickshoe a velmi podrobně vysvětlenou v článek citoval, je provést proces řazení a stránkování ve dvou fázích:v první fázi musíte vytvořit dotaz, který bude odkazovat na vaše podřízené entity a ve kterém použijete stránkování a řazení. Tento dotaz vám umožní identifikovat ID hlavních entit, které budou použity ve druhé fázi procesu k získání samotných hlavních entit.
K provedení tohoto procesu můžete využít výše uvedené vlastní DAO.