Как правильно выразить JPQL "join fetch" с предложением "where" как JPA 2 CriteriaQuery? - PullRequest
43 голосов
/ 28 апреля 2011

Рассмотрим следующий запрос JPQL:

SELECT foo FROM Foo foo
INNER JOIN FETCH foo.bar bar
WHERE bar.baz = :baz

Я пытаюсь перевести это в запрос Critieria.Это насколько я получил:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> r = cq.from(Foo.class);
Fetch<Foo, Bar> fetch = r.fetch(Foo_.bar, JoinType.INNER);
Join<Foo, Bar> join = r.join(Foo_.bar, JoinType.INNER);
cq.where(cb.equal(join.get(Bar_.baz), value);

Очевидная проблема здесь в том, что я делаю одно и то же соединение дважды, потому что Fetch<Foo, Bar>, похоже, не имеет метода для получения Path,Есть ли способ избежать необходимости присоединиться дважды?Или я должен придерживаться старого доброго JPQL с таким простым запросом?

Ответы [ 2 ]

65 голосов
/ 28 апреля 2011

В JPQL то же самое действительно верно в спецификации.Спецификация JPA не разрешает давать псевдоним соединению выборки.Проблема в том, что вы можете легко выстрелить себе в ногу с помощью ограничения контекста соединения.Безопаснее присоединиться дважды.

Обычно это больше проблема с ToMany, чем с ToOnes.Например,

Select e from Employee e 
join fetch e.phones p 
where p.areaCode = '613'

Это неверно вернет всех Сотрудников, которые содержат номера в коде области '613', но пропустят номера телефонов других областей в возвращенном списке.Это означает, что сотрудник, у которого был телефон с телефонными кодами 613 и 416, потеряет номер телефона 416, поэтому объект будет поврежден.

Конечно, если вы знаете, что делаете, дополнительное соединениенежелательно, некоторые провайдеры JPA могут разрешить наложение псевдонима для соединения и могут разрешить приведение Criteria Fetch к соединению.

7 голосов
/ 04 июля 2018

Я наглядно покажу проблему, используя отличный пример ответа Джеймса и добавив альтернативное решение.

Когда вы выполните следующий запрос, без FETCH:

Select e from Employee e 
join e.phones p 
where p.areaCode = '613'

Вы получите следующие результаты от Employee, как и ожидали:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613
1          | James        | 6       | 416

Но когда вы добавите слово FETCH в JOIN, вот что произойдет:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613

Сгенерированный SQL одинаков для двух запросов, но Hibernate удаляет в памяти регистр 416 при использовании WHERE в соединении FETCH.

Так, чтобы привести все телефоны и для правильного применения WHERE, необходимо иметь два JOIN s: один для WHERE и другой для FETCH.Как:

Select e from Employee e 
join e.phones p 
join fetch e.phones      //no alias, to not commit the mistake
where p.areaCode = '613'
...