web-dev-qa-db-fra.com

Comment exprimer correctement JPQL "join fetch" avec la clause "where" comme JPA 2 CriteriaQuery?

Considérez la requête JPQL suivante:

SELECT foo FROM Foo foo
INNER JOIN FETCH foo.bar bar
WHERE bar.baz = :baz

J'essaie de traduire cela en une requête Critieria. Voici ce que j'ai obtenu:

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

Le problème évident ici est que je fais deux fois la même jointure, car Fetch<Foo, Bar> ne semble pas avoir de méthode pour obtenir un Path. Existe-t-il un moyen d'éviter d'avoir à adhérer deux fois? Ou dois-je m'en tenir au bon vieux JPQL avec une requête aussi simple que ça?

47
chris

Dans JPQL, la même chose est en fait vraie dans la spécification. La spécification JPA ne permet pas de donner un alias à une jointure d'extraction. Le problème est que vous pouvez facilement vous tirer une balle dans le pied en limitant le contexte de l'extraction de jointure. Il est plus sûr de se joindre deux fois.

C'est normalement plus un problème avec ToMany que ToOnes. Par exemple,

Select e from Employee e 
join fetch e.phones p 
where p.areaCode = '613'

Cela incorrectement retournera tous les employés qui contiennent des numéros dans l'indicatif régional '613' mais laissera de côté les numéros de téléphone des autres zones dans la liste retournée. Cela signifie qu'un employé qui avait un téléphone dans les indicatifs régionaux 613 et 416 perdra le numéro de téléphone 416, donc l'objet sera corrompu.

Certes, si vous savez ce que vous faites, la jointure supplémentaire n'est pas souhaitable, certains fournisseurs JPA peuvent autoriser l'alias de l'extraction de jointure et autoriser la conversion de l'extraction des critères en une jointure.

66
James

Je vais montrer visuellement le problème, en utilisant le grand exemple de la réponse de James et en ajoutant la solution alternative.

Lorsque vous effectuez la requête suivante, sans le FETCH:

Select e from Employee e 
join e.phones p 
where p.areaCode = '613'

Vous obtiendrez les résultats suivants de Employee comme prévu:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613
1          | James        | 6       | 416

Mais lorsque vous ajoutez le FETCH Word sur JOIN, voici ce qui se passe:

EmployeeId | EmployeeName | PhoneId | PhoneAreaCode
1          | James        | 5       | 613

Le SQL généré est le même pour les deux requêtes, mais la mise en veille prolongée supprime en mémoire le 416 enregistrez-vous lorsque vous utilisez WHERE sur la jointure FETCH.

Donc, pour apporter tous les téléphones et appliquer correctement le WHERE, vous devez avoir deux JOINs: un pour le WHERE et un autre pour le FETCH. Comme:

Select e from Employee e 
join e.phones p 
join fetch e.phones      //no alias, to not commit the mistake
where p.areaCode = '613'
7
Dherik