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.