Используя API-интерфейс JPA Criteria, можете ли вы выполнить выборку, в результате чего получается только одно соединение?

Использование JPA 2.0. По-видимому, по умолчанию (без явной выборки) поля @OneToOne(fetch = FetchType.EAGER) выбираются в 1 + N запросах, где N - количество результатов, содержащих объект, который определяет отношение к отдельному связанному объекту. Используя API критериев, я могу попытаться избежать этого следующим образом:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
root.fetch("relatedEntity");
query.select(root).where(builder.equals(join.get("id"), 3));

В идеале это должно быть эквивалентно следующему:

SELECT m FROM MyEntity m JOIN FETCH myEntity.relatedEntity r WHERE r.id = 3

Однако запрос критериев приводит к тому, что корневая таблица без необходимости соединяется с таблицей связанных сущностей дважды; один раз для извлечения и один раз для предиката. Полученный SQL выглядит примерно так:

SELECT myentity.id, myentity.attribute, relatedentity2.id, relatedentity2.attribute 
FROM my_entity myentity 
INNER JOIN related_entity relatedentity1 ON myentity.related_id = relatedentity1.id 
INNER JOIN related_entity relatedentity2 ON myentity.related_id = relatedentity2.id 
WHERE relatedentity1.id = 3

Увы, если я только делаю выборку, то у меня нет выражения для использования в предложении where.

Я что-то упустил, или это ограничение API критериев? Если это последнее, исправляется ли это в JPA 2.1 или существуют ли какие-либо улучшения для конкретного поставщика?

В противном случае, кажется, лучше просто отказаться от проверки типа компиляции (я понимаю, что мой пример не использует метамодель) и использует динамический JPQL TypedQueries.

Ответы

Ответ 1

Вместо root.join(...) вы можете использовать root.fetch(...), который возвращает объект Fetch<>.

Fetch<> является потомком Join<>, но его можно использовать аналогичным образом.

Вам просто нужно отбрасывать Fetch<> в Join<>, он должен работать для EclipseLink и Hibernate

...
Join<MyEntity, RelatedEntity> join = (Join<MyEntity, RelatedEntity>)root.fetch("relatedEntity");
...

Ответ 2

Начиная с JPA 2.1, вы можете использовать для этого динамические графы объектов. Удалите вашу выборку и укажите граф сущности следующим образом:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> query = builder.createQuery(MyEntity.class);
Root<MyEntity> root = query.from(MyEntity.class);
Join<MyEntity, RelatedEntity> join = root.join("relatedEntity");
query.select(root).where(builder.equal(join.get("id"), 3));
EntityGraph<MyEntity> fetchGraph = entityManager.createEntityGraph(MyEntity.class);
fetchGraph.addSubgraph("relatedEntity");
entityManager.createQuery(query).setHint("javax.persistence.loadgraph", fetchGraph);

Ответ 3

Использование root.fetch() в EclipseLink создаст SQL с INNER JOIN, потому что есть 3 типа, а по умолчанию INNER.

INNER,  LEFT,  RIGHT;

Предполагается использовать CreateQuery.

TypedQuery<T> typedQuery = entityManager.createQuery(query);

РЕДАКТИРОВАТЬ: вы можете вставить корень в From следующим образом:

From<?, ?> join = (From<?, ?>) root.fetch("relatedEntity");