¿Cómo express adecuadamente “juntar captar” JPQL con la cláusula “where” como JPA 2 CriteriaQuery?

Considere la siguiente consulta JPQL:

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

Estoy tratando de traducir esto en una consulta de Critieria. Esto es todo lo que he conseguido:

 CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Foo.class); Root r = cq.from(Foo.class); Fetch fetch = r.fetch(Foo_.bar, JoinType.INNER); Join join = r.join(Foo_.bar, JoinType.INNER); cq.where(cb.equal(join.get(Bar_.baz), value); 

El problema obvio aquí es que estoy haciendo lo mismo unirme dos veces, porque Fetch no parece tener un método para obtener una Path . ¿Hay alguna forma de evitar tener que unirte dos veces? ¿O tengo que seguir con el buen viejo JPQL con una consulta tan simple como esa?

En JPQL, lo mismo es cierto en la especificación. La especificación JPA no permite que se proporcione un alias a una unión de búsqueda. El problema es que puedes disparar fácilmente en el pie con esto al restringir el contexto de la búsqueda de unión. Es más seguro unirse dos veces.

Esto normalmente es más un problema con ToMany que ToOnes. Por ejemplo,

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

Esto devolverá incorrectamente a todos los empleados que contengan números en el código de área ‘613’, pero omitirá los números de teléfono de otras áreas en la lista devuelta. Esto significa que un empleado que tenía un teléfono en los códigos de área 613 y 416 perderá el número de teléfono 416, por lo que el objeto estará dañado.

Por supuesto, si sabe lo que está haciendo, la unión adicional no es deseable, algunos proveedores de JPA pueden permitir el aliasing fetch de búsqueda, y puede permitir el lanzamiento de Criteria Fetch a Join.

Mostraré visualmente el problema, utilizando el gran ejemplo de la respuesta de James y agregando la solución alternativa.

Cuando haces la siguiente consulta, sin el FETCH :

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

Tendrá los siguientes resultados del Employee como esperaba:

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

Pero cuando agrega la palabra FETCH en JOIN , esto es lo que sucede *:

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

Entonces, para traer todos los teléfonos y aplicar WHERE , necesita tener dos JOIN : uno para WHERE y otro para FETCH . Me gusta:

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

*. El SQL original trae los mismos resultados de la consulta sin FETCH , pero Hibernate elimina en la memoria el registro 416 cuando usa FETCH con WHERE .