web-dev-qa-db-fra.com

À l'aide de l'API JPA Criteria, pouvez-vous effectuer une jointure d'extraction qui se traduit par une seule jointure?

Utilisation de JPA 2.0. Il semble que par défaut (pas de récupération explicite), les champs @OneToOne(fetch = FetchType.EAGER) sont récupérés dans des requêtes 1 + N, où N est le nombre de résultats contenant une entité qui définit la relation avec une entité liée distincte. En utilisant l'API Criteria, je pourrais essayer d'éviter cela comme suit:

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));

Idéalement, ce qui précède devrait être équivalent à ce qui suit:

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

Cependant, la requête de critères entraîne la jointure inutile de la table racine à la table d'entités associée deux fois; une fois pour le fetch, et une fois pour le prédicat where. Le SQL résultant ressemble à ceci:

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

Hélas, si je ne fais que la récupération, je n'ai pas d'expression à utiliser dans la clause where.

Suis-je en train de manquer quelque chose, ou s'agit-il d'une limitation de l'API Criteria? Si c'est le dernier, cela est-il corrigé dans JPA 2.1 ou y a-t-il des améliorations spécifiques au fournisseur?

Sinon, il semble préférable de simplement abandonner la vérification de type à la compilation (je me rends compte que mon exemple n'utilise pas le métamodèle) et d'utiliser des JPQ TypedQueries dynamiques.

22
Shaun

Au lieu de root.join(...), vous pouvez utiliser root.fetch(...) qui renvoie Fetch<> objet.

Fetch<> est un descendant de Join<> mais il peut être utilisé de manière similaire.

Vous avez juste besoin de lancer Fetch<> à Join<> cela devrait fonctionner pour EclipseLink et Hibernate

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

À partir de JPA 2.1, vous pouvez utiliser des graphiques d'entités dynamiques pour cela. Supprimez votre extraction et spécifiez un graphique d'entité comme suit:

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);
6
Darren Reimer

L'utilisation de root.fetch() sur EclipseLink créera un SQL avec INNER JOIN car il existe 3 types et la valeur par défaut est INNER.

INNER,  LEFT,  RIGHT;

La suggestion est d'utiliser CreateQuery.

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

EDIT: vous pouvez convertir la racine en De comme ceci:

From<?, ?> join = (From<?, ?>) root.fetch("relatedEntity");
1
Fábio Almeida