Как сделать "глубокое" соединение для получения в 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 поддерживают его, и ваш код остается независимым от поставщика.