Как правильно выразить JPQL "join fetch" с предложением "where" как JPA 2 CriteriaQuery?
Рассмотрим следующий запрос 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 с таким простым запросом?
Ответы
Ответ 1
В 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 к соединению.
Ответ 2
Я наглядно продемонстрирую проблему, используя отличный пример ответа Джеймса и добавив альтернативное решение.
Когда вы делаете следующий запрос, без 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
: один для 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'