То, что здесь происходит, можно очень четко увидеть, включив отладку SQL в Hibernate и сравнив сгенерированные запросы.
Используя довольно простое Sale
→ Item
отображение один-ко-многим (которое, мы надеемся, самоочевидно), запрос на основе Criteria
, подобный этому:
Criteria c = sessionFactory.getCurrentSession().createCriteria(Sale.class);
c.createAlias("items", "i");
c.add(Restrictions.eq("i.name", "doll"));
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
c.setMaxResults(2);
создает SQL следующим образом:
select top ? this_.saleId as saleId1_1_, ...
from Sale this_
inner join Sale_Item items3_ on this_.saleId=items3_.Sale_saleId
inner join Item items1_ on items3_.items_id=items1_.id
where items1_.name=?
тогда как Query
вот так:
Query q = sessionFactory.getCurrentSession().createQuery("select distinct s from Sale s join s.items as i where i.name=:name");
q.setParameter("name", "doll");
q.setMaxResults(2);
производит что-то вроде:
select top ? distinct hibernated0_.saleId as saleId1_
from Sale hibernated0_
inner join Sale_Item items1_ on hibernated0_.saleId=items1_.Sale_saleId
inner join Item hibernated2_ on items1_.items_id=hibernated2_.id
where hibernated2_.name=?
Обратите внимание на разницу в самой первой строке (DISTINCT
). ResultTransformer
подобно DISTINCT_ROOT_ENTITY
- это класс Java, который обрабатывает результаты строк SQL после выполнения SQL. Поэтому, когда вы указываете maxResults
, это будет применяться как ограничение строки в SQL; SQL включает соединение с элементами Collection
, поэтому вы ограничиваете свой результат SQL до 90 подэлементов После применения преобразователя DISTINCT_ROOT_ENTITY
это может привести к появлению менее 20 корневых элементов, в зависимости от того, какие корневые элементы окажутся первыми в 90 соединенных результатах.
DISTINCT
в HQL ведет себя очень по-разному, в том смысле, что фактически используется ключевое слово SQL DISTINCT
, которое применяется до ограничения строки. Таким образом, это ведет себя так, как вы ожидаете, и объясняет разницу между 2.
Теоретически вы должны смотреть setProjection
, чтобы применить проекцию на уровне SQL - что-то вроде c.setProjection(Projections.distinct(Projections.rootEntity()))
- но, к сожалению, Projections.rootEntity()
не существует, я только что придумал. Возможно, так и должно быть!