JPQL Joins
Los tipos de joins fueron tomados de sql, pero se muestra entre paréntesis cómo pueden ser abreviados en jpql:
Este query retorna todas las instancias de la entidad Phone
asociadas con employees
en la compañía:
SELECT p
FROM Employee e JOIN e.phones p
Fetch joins are intended to help application designers optimize their database access and prepare query results for detachment. They allow queries to specify one or more relationships that should be navigated and prefetched by the query engine so that they are not lazy loaded later at runtime.
fetch sirve para cargar asociaciones de manera fetch=eager para que puedan ser utilizadas cuando el objeto esté detached. Este tipo de join sobreescribe cualquier comportamiento del atributo fetch definido en las anotaciones o en los archivos de mapeo
For example, if we have an Employee entity with a lazy loading relationship to its address, the following query can be used to indicate that the relationship should be resolved eagerly during query execution:
SELECT e
FROM Employee e JOIN FETCH e.address
Otro ejemplo:
select q from Question q inner join fetch q.options
En la práctica fetch join
sirve para cargar asociaciones filtradas en el objeto padre. Considere el siguiente ejemplo:
// obtener todas las preguntas (con las opciones cargadas) que contengan opciones con el texto 'wonderful'.
List<Question> list = JPA.em().createQuery(
"select q from Question q inner join fetch q.options opt where opt.text like '%wonderful'",
Question.class).getResultList();
/*
Se debe utilizar fetch para cargar en la pregunta las opciones filtradas en el query, el query solo traerá las preguntas que tengan opciones con texto wonderful y al consultar las opciones de cada pregunta sólo estarán presentes las que tengan el texto wonderful.
Si no se utiliza fetch en el query y se consultan las opciones, ejecutará un query lazy para obtener las opciones y traerá todas sin importar el filtro de la consulta.
*/
Logger.info("questions.size() = %s", list.size());
for (Question q : list) {
Logger.info("question = %s", q);
Logger.info("q.options = %s", q.options);
}
/*
Otra alternativa para resolver este problema sin fetch:
select q, opt from Question q inner join q.options opt where opt.text like '%wonderful'
*/
No se recomienda utilizar fetch en los queries sólo porque se desea cargar la colección inmediatamente, utilice fetch cuando así lo requiera la lógica del query como por ejemplo para cargar los hijos filtrados en un padre que se encuentra en el select.
Una forma de escribir un join es a través de la relación entre dos objetos:
SELECT e
FROM Project p JOIN p.employees e
Otra forma es cuando los objetos no tienen una relación entre sí:
SELECT d, m
FROM Department d, Employee m
WHERE d = m.department
This query uses the JOIN operator to join the Employee entity to the Phone entity across the phones relationship:
SELECT p FROM Employee e JOIN e.phones p
En el query anterior p
se refiere a cada phone dentro de la colección e.phones
, y e.phones
se refiere a la colección.
Ejemplo filtrando los objetos de la colección:
`select q from Question q inner join fetch q.options o where o.alias='opt1'``
Ejemplo filtrando la colección:
select q from Question q inner join fetch q.options o where q.options.size>0
Join conditions can be specified explicitly, such as using the JOIN operator in the FROM clause of a query, or implicitly as a result of path navigation
Por lo tanto los siguientes queries son equivalentes:
SELECT d FROM Employee e JOIN e.department d
SELECT e.department FROM Employee e
Path navigation from one entity to another is a form of inner join. The outer join of two entities is the set of objects from both entity types that satisfy the join conditions plus the set of objects from one entity type (designated as the left entity) that have no matching join condition in the other.
Las rutas de navegación son equivalentes a inner joins de todas las entidades asociadas en el path: p.employees
genera un inner join entre Project
y Employee
.
El siguiente query no tiene 2 inner joins, sino 4.
Versión implicita (2 joins lógicos):
SELECT DISTINCT e.department
FROM Project p JOIN p.employees e
WHERE p.name = 'Release1' AND
e.address.state = 'CA'
Versión explicita (4 joins lógicos):
SELECT DISTINCT d
FROM Project p JOIN p.employees e JOIN e.department d JOIN e.address a
WHERE p.name = 'Release1' AND
a.state = 'CA'
En los joins hay que tener cuidado cuando se filtran hijos definidos como lazy.
Si se crea un query con joins pero solo se está seleccionando el objeto padre, al consultar las colecciones hijas volverá a ejecutar un select para obtener los elementos, NO TRAERÁ LOS RESULTADOS FILTRADOS DE LA COLECCIÓN EN EL QUERY. Ejemplo:
select q from Question q inner join q.options opt where opt.text like '%wonderful'
El query obtiene las preguntas que tengan opciones con el texto 'wonderful', sin embargo como las opciones están definidas como 'fetch=lazy', cuando se pidan las opciones q.options
ejecutará un query que obtendrá todas las opciones de la pregunta en vez de solo las filtradas (las que tienen el texto wonderful).
Existen 2 alternativas para resolver el problema.
Alternativa 1: utilizar fetch en el join hará que cargue las opciones filtradas en el query, al ser un fetch join significa que sólo cargará las opciones filtradas en el query:
select q from Question q inner join fetch q.options opt where opt.text like '%wonderful'
Alternativa 2: utilizar inner join y en el select las opciones y la pregunta de cada opción:
"select opt, q from Question q inner join q.options opt where opt.text like '%wonderful'
Sirve para traer todas las propiedades de un objeto con la estrategia fetch=eager.
from Document fetch all properties order by name