Как сделать "глубокое" соединение для получения в JPQL?
Я не думаю, что когда-либо полностью пойму, чтобы присоединиться к ним.
У меня есть запрос, где я пытаюсь "раздуть" ссылки на два уровня.
То есть мой A
имеет необязательный Collection
of B
s, и каждый B
имеет либо 0, либо 1 C
. Известно, что размер коллекции B
мал (10-20 вершин). Я хотел бы предварительно выбрать этот график.
A
B
Отмечается как FetchType.LAZY
и является необязательным. B
отношение к C
также необязательно и FetchType.LAZY
.
Я надеялся, что смогу сделать:
SELECT a
FROM A a
LEFT JOIN FETCH a.bs // look, no alias; JPQL forbids it
LEFT JOIN a.bs b // "repeated" join necessary since you can't alias fetch joins
LEFT JOIN FETCH b.c // this doesn't seem to do anything
WHERE a.id = :id
Когда я запускаю это, я вижу, что набор A
B
действительно выбран (я вижу LEFT JOIN
в SQL, ссылающемся на таблицу, на которую отображается B
).
Однако я не вижу таких доказательств, что таблица C
извлекается.
Как я могу предварительно выбрать все C
и все B
и все C
, которые "достижимы" из заданного A
? Я не вижу никакого способа сделать это.
Ответы
Ответ 1
Спецификация JPA не позволяет сглаживать соединение для извлечения, но некоторые поставщики JPA делают.
EclipseLink делает с 2.4. EclipseLink также разрешает вложенную выборку соединения с использованием точечной нотации (например, "JOIN FETCH a.bs.c" ) и поддерживает подсказку "eclipselink.join-fetch", которая позволяет вложенные соединения (вы можете указать несколько намеков на одно и то же имя подсказки).
В общем, вам нужно быть осторожным при использовании псевдонима в соединении извлечения, так как вы можете повлиять на возвращаемые данные.
См,
http://java-persistence-performance.blogspot.com/2012/04/objects-vs-data-and-filtering-join.html
Ответ 2
JPA не допускает вложенных попыток присоединения, а также не допускает псевдоним соединения fetch, так что это, вероятно, специфический поставщик JPA.
В EclipseLink вы можете указать подсказку запроса для выполнения вложенного соединения выбирает.
Вы не можете сделать его рекурсивным в JPQL, хотя вы могли бы пойти только в лучшем случае n уровней. В EclipseLink вы можете использовать @JoinFetch или @BatchFetch на отображение, чтобы сделать запрос рекурсивным.
См, http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
Источник: http://www.coderanch.com/t/570828/ORM/databases/Recursive-fetch-join-recursively-fetching
Ответ 3
Я использую Hibernate (и это может быть специфично для него), и у меня был успех с этим:
SELECT DISTINCT a, b
FROM A a
LEFT JOIN a.bs b
LEFT JOIN FETCH a.bs
LEFT JOIN FETCH b.c
WHERE a.id = :id
(Обратите внимание на b
в списке выбора).
Это был единственный способ, которым я нашел, что это сработает для меня, обратите внимание, что это возвращает Object[]
для меня, и я затем отфильтровываю его в коде так:
(List<A>) q.getResultList().stream().map(pair -> (A) (((Object[])pair)[0])).distinct().collect(Collectors.toList());
Ответ 4
Не совсем JPQL, но вы можете достичь этого в чистом JPA с запросами Criteria:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> q = cb.createQuery(MyEntity.class);
Root<MyEntity> root = q.from(MyEntity.class);
q.select(root);
Fetch bsFetch = root.fetch("bs", JoinType.LEFT);
bsFetch.fetch("c", JoinType.LEFT);
Поддержка такого типа вложенных выборок зависит от поставщика (поскольку JPA не требует от них этого), но eclipselink и hibernate поддерживают его, и ваш код остается независимым от поставщика.