Проблемът, който експериментирате, е свързан с начина, по който използвате HINT_PASS_DISTINCT_THROUGH
намек.
Тази подсказка ви позволява да посочите на Hibernate, че DISTINCT
ключова дума не трябва да се използва в SELECT
извлечение, издадено срещу базата данни.
Вие се възползвате от този факт, за да позволите заявките ви да бъдат сортирани по поле, което не е включено в DISTINCT
списък с колони.
Но този съвет не трябва да се използва така.
Тази подсказка трябва да се използва само когато сте сигурни, че няма да има разлика между прилагането или не на DISTINCT
ключова дума към SQL SELECT
израз, защото SELECT
изразът вече ще извлече всички отделни стойности per se . Идеята е да се подобри производителността на заявката, като се избягва използването на ненужен DISTINCT
изявление.
Това обикновено се случва, когато използвате query.distinct
метод във вашите заявки за критерии и сте join fetching
детски отношения. Тази страхотна статия
на @VladMihalcea обяснете подробно как работи подсказката.
От друга страна, когато използвате страниране, той ще зададе OFFSET
и LIMIT
- или нещо подобно, в зависимост от основната база данни - в SQL SELECT
изявление, издадено срещу базата данни, ограничаващо до максимален брой резултати на вашата заявка.
Както е посочено, ако използвате HINT_PASS_DISTINCT_THROUGH
намек, SELECT
изразът няма да съдържа DISTINCT
ключова дума и, поради вашите съединявания, потенциално може да даде дублиращи се записи на основния ви обект. Тези записи ще бъдат обработени от Hibernate за разграничаване на дубликати, защото използвате query.distinct
, и всъщност ще премахне дубликати, ако е необходимо. Мисля, че това е причината, поради която може да получите по-малко записи от изискваните във вашия Pageable
.
Ако премахнете подсказката, като DISTINCT
ключовата дума се предава в SQL израза, който се изпраща към базата данни, доколкото проектирате само информация за основния обект, тя ще извлече всички записи, посочени от LIMIT
и ето защо винаги ще ви дава искания брой записи.
Можете да опитате да fetch join
вашите дъщерни обекти (вместо само join
с тях). Това ще елиминира проблема с невъзможността да използвате полето, по което трябва да сортирате в колоните на DISTINCT
ключова дума и в допълнение ще можете да приложите, вече законно, подсказката.
Но ако го направите, ще имате друг проблем:ако използвате извличане на присъединяване и страниране, за да върнете основните обекти и колекциите им, Hibernate вече няма да прилага страниране на ниво база данни - няма да включва OFFSET
или LIMIT
ключови думи в SQL оператора и ще се опита да пагинира резултатите в паметта. Това е известният Hibernate HHH000104
предупреждение:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
@VladMihalcea обяснява това много подробно в последната част на това статия.
Той също така предложи едно възможно решение на вашия проблем, Window Functions .
В случай на използване, вместо да използвате Specification
s, идеята е да внедрите свой собствен DAO. Този DAO трябва да има достъп само до EntityManager
, което не е много, тъй като можете да инжектирате своя @PersistenceContext
:
@PersistenceContext
protected EntityManager em;
След като имате този EntityManager
, можете да създавате собствени заявки и да използвате прозоречни функции за изграждане, въз основа на предоставения Pageable
информация, правилният SQL оператор, който ще бъде издаден срещу базата данни. Това ще ви даде много повече свобода за това какви полета използват за сортиране или каквото и да е необходимо.
Както показва последната цитирана статия, Window Functions е функция, поддържана от всички кметски бази данни.
В случая с PostgreSQL можете лесно да ги намерите в официалната документация .
И накрая, още една опция, предложена всъщност от @nickshoe и обяснена много подробно в статия той цитира, е да извършите процеса на сортиране и страниране в две фази:в първата фаза трябва да създадете заявка, която ще препраща към вашите дъщерни обекти и в която ще приложите страниране и сортиране. Тази заявка ще ви позволи да идентифицирате идентификаторите на основните обекти, които ще бъдат използвани във втората фаза на процеса за получаване на самите основни обекти.
Можете да се възползвате от гореспоменатия персонализиран DAO, за да изпълните този процес.