La idea en este tutorial es mostrar el lenguaje de queries JPA QL o JPQL (el lenguaje de queries de JPA). y como se ejecuta por detras el SQL. En primer lugar vamos a hacer un repaso del leguaje de queries de Hibernate HQL, siendo JPAQL un subset de HQL y luego vamos a profundizar en las queries de JPA.
Tanto las queries en HQL como en JPQL son expresadas en objetos java en lugar de tablas y columnas.
El EntityManager de JPA usa la configuracion JDBC especificada en el persistence.xml (mas adelante se profundizara sobre estos temas) para convertir una consulta JPA en una consulta SQL nativa.
La idea principal de este tutorial es poder escribir una query en JPAQL y saber que es lo que esta haciendo por detras. Es decir, que consulta SQL esta ejecutando el framework
Al final de este tutorial, el lector encontrara un link para descargar los ejemplos vistos en todos los tutoriales JPA de este blog.
Tanto las queries en HQL como en JPQL son expresadas en objetos java en lugar de tablas y columnas.
El EntityManager de JPA usa la configuracion JDBC especificada en el persistence.xml (mas adelante se profundizara sobre estos temas) para convertir una consulta JPA en una consulta SQL nativa.
La idea principal de este tutorial es poder escribir una query en JPAQL y saber que es lo que esta haciendo por detras. Es decir, que consulta SQL esta ejecutando el framework
Al final de este tutorial, el lector encontrara un link para descargar los ejemplos vistos en todos los tutoriales JPA de este blog.
Temas a tratar:
- HQL alternativas de creacion de queries con el objeto Session
- HQL ejemplos basicos
- JPAQL ejemplo basico
- JPAQL consulta con proyeccion
- JPAQL consulta con condicionales
- Comparativa entre operadores aritmeticos de HQL y JPAQL
- JPAQL funciones de agregacion (AVG, MAX, SUM, COUNT)
- GROUP BY
- HAVING
- JOINS, INNER JOINS, LEFT OUTER JOINS, INNER JOINS FETCH
- JPAQL Identificadores reservados
1. HQL
Existen 3 caminos distintos para expresar queries en Hibernate
Vamos a tomar como ejemplo, la entidad Account creada en el tutorial I de JPA (Ver tutorial JPA I Manejo de Relaciones)
■ 3 Alternativas de armar queries en HQL
A.- Hibernate Query Language (HQL):
List list = session.createQuery("from Account a where a.balance=100").list();
SQL Trace:
account0_.ACCOUNT_ID as ACCOUNT2_3_,
account0_.balance as balance3_,
account0_.CUSOMTER_ID as CUSOMTER6_3_,
account0_.overdraftLimit as overdraf4_3_,
account0_.interestRate as interest5_3_,
account0_.ACCOUNT_TYPE as ACCOUNT1_3_
from
Account account0_
where
account0_.balance=100
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_2_,
customer0_.FIRST_NAME as FIRST2_4_2_,
customer0_.GENDER as GENDER4_2_,
customer0_.LAST_NAME as LAST4_4_2_,
customer0_.referee_id as referee7_4_2_,
customer0_.name1 as name5_4_2_,
customer0_.name2 as name6_4_2_,
accounts1_.CUSOMTER_ID as CUSOMTER6_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts1_.balance as balance3_0_,
accounts1_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts1_.overdraftLimit as overdraf4_3_0_,
accounts1_.interestRate as interest5_3_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_3_0_,
referee2_.id as id2_1_,
referee2_.comments as comments2_1_,
referee2_.name as name2_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
B.- Criteria API for query by criteria (QBC) and query by example (QBE):
List
SQL Trace:
select
this_.ACCOUNT_ID as ACCOUNT2_3_2_,
this_.balance as balance3_2_,
this_.CUSOMTER_ID as CUSOMTER6_3_2_,
this_.overdraftLimit as overdraf4_3_2_,
this_.interestRate as interest5_3_2_,
this_.ACCOUNT_TYPE as ACCOUNT1_3_2_,
customer2_.CUSTOMER_ID as CUSTOMER1_4_0_,
customer2_.FIRST_NAME as FIRST2_4_0_,
customer2_.GENDER as GENDER4_0_,
customer2_.LAST_NAME as LAST4_4_0_,
customer2_.referee_id as referee7_4_0_,
customer2_.name1 as name5_4_0_,
customer2_.name2 as name6_4_0_,
referee3_.id as id2_1_,
referee3_.comments as comments2_1_,
referee3_.name as name2_1_
from
Account this_
left outer join
Customer customer2_
on this_.CUSOMTER_ID=customer2_.CUSTOMER_ID
left outer join
Referee referee3_
on customer2_.referee_id=referee3_.id
where
this_.balance=?
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Para profundizar en este tema, ver http://docs.jboss.org/hibernate/core/3.5/reference/es-ES/html/querycriteria.html
C.- Direct SQL with or without automatic mapping of resultsets to objects:
List
SQL Trace:
select
a.ACCOUNT_ID as ACCOUNT2_3_0_,
a.balance as balance3_0_,
a.CUSOMTER_ID as CUSOMTER6_3_0_,
a.overdraftLimit as overdraf4_3_0_,
a.interestRate as interest5_3_0_,
a.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
account a
where
balance=100
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_2_,
customer0_.FIRST_NAME as FIRST2_4_2_,
customer0_.GENDER as GENDER4_2_,
customer0_.LAST_NAME as LAST4_4_2_,
customer0_.referee_id as referee7_4_2_,
customer0_.name1 as name5_4_2_,
customer0_.name2 as name6_4_2_,
accounts1_.CUSOMTER_ID as CUSOMTER6_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts1_.balance as balance3_0_,
accounts1_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts1_.overdraftLimit as overdraf4_3_0_,
accounts1_.interestRate as interest5_3_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_3_0_,
referee2_.id as id2_1_,
referee2_.comments as comments2_1_,
referee2_.name as name2_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
Para profundizar en este tema, ver http://docs.jboss.org/hibernate/core/3.5/reference/es-ES/html/querysql.html
En los 3 casos, podemos observar que respetan a la perfeccion lo que se definio en el mapeo de la entidad Account (Ver tutorial JPA I Manejo de Relaciones). Donde la relacion N a 1 entre Account y Customer tiene por defecto una lectura temprana (ya que este tipo de relaciones, al no explicitar ningun valor, toman por defecto la lectura temprana), es decir, esta seteada por default a EAGER.
En caso que la relacion entre Account y Customer hubiere tenido lectura tardia (LAZY)
....
.....
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="CUSOMTER_ID",
referencedColumnName="CUSTOMER_ID"
)
private Customer customer;
....
....
Al ejecutar las consultas anteriores, el sql trace hubiese sido el siguiente:
account0_.ACCOUNT_ID as ACCOUNT2_3_,
account0_.balance as balance3_,
account0_.CUSOMTER_ID as CUSOMTER6_3_,
account0_.overdraftLimit as overdraf4_3_,
account0_.interestRate as interest5_3_,
account0_.ACCOUNT_TYPE as ACCOUNT1_3_
from
Account account0_
where
account0_.balance=100
■ Bindeando parametros
a.- Usando named parameters
String queryString ="from Customer c where c.firstName = :name";
Query q = session.createQuery(queryString).setString("name", "First name 1");
List
b.- Usando poositional parameters
String queryString2 ="from Customer c where c.firstName = ?";
Query q2 = session.createQuery(queryString2).setString(0, "First name 1");
List
■ Query Hints (modificar la configueracion de los objetos de persistencia antes de ejecutar la query)
Query q = session.createQuery(queryString2).setFlushMode(FlushMode.COMMIT);
Query q = session.createQuery(queryString2).setTimeout(60);
■ Unico resultado
Account a = (Account) session.createQuery("from Account ").setMaxResults(1).uniqueResult();
■ Expresiones de comparacion
List
List
ids.add(1);
ids.add(3);
List
Para profundizar en HQL, ver http://docs.jboss.org/hibernate/core/3.5/reference/es-ES/html/queryhql.html
2. JPA QL
Antes de empezar, vamos a tomar como ejemplo las siguientes tablas y sus registros sobre las cuales vamos a realizar los ejemplos, sobre todo, para que se entiendan las queries en SQL que se lanzan a la hora de ejecutar las consultas en JPQL
TABLA CUSTOMER
-------------- ------------- --------- ------------ -------- -------- -------------
1 First name 1 FEMALE Last name 1 Damian (null) 1
2 First name 2 FEMALE Last name 2 (null) (null) 2
3 First name 3 FEMALE Last name 3 (null) (null) 1
4 First name 4 MALE Last name 4 (null) (null) (null)
TABLA REFEREE
ID COMMENTS NAME
----- ----------- ---------
1 comments 1 Referee 1
2 comments 2 Referee 2
TABLA ACCOUNTS
ACCOUNT_TYPE ACCOUNT_ID BALANCE OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID
--------------- ------------- ---------- ----------------- --------------- --------------
C 1 10 0 (null) 1
C 2 20 0 (null) 1
S 3 100 (null) 0 1
----- ----------- ---------
1 comments 1 Referee 1
2 comments 2 Referee 2
TABLA ACCOUNTS
ACCOUNT_TYPE ACCOUNT_ID BALANCE OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID
--------------- ------------- ---------- ----------------- --------------- --------------
C 1 10 0 (null) 1
C 2 20 0 (null) 1
S 3 100 (null) 0 1
TABLA CUSTOMER_ADDRESS
CUST_ID ADD_ID
---------- --------
1 2
1 0
■ Simple Query
Query query = em.createQuery("SELECT c from Customer c");
List customers = query.getResultList();
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_,
customer0_.FIRST_NAME as FIRST2_4_,
customer0_.GENDER as GENDER4_,
customer0_.LAST_NAME as LAST4_4_,
customer0_.referee_id as referee7_4_,
customer0_.name1 as name5_4_,
customer0_.name2 as name6_4_
from
Customer customer0_
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
■ Projection Query
Query query = em.createQuery("SELECT c.firstName, c.lastName from Customer c");select
customer0_.CUSTOMER_ID as CUSTOMER1_4_,
customer0_.FIRST_NAME as FIRST2_4_,
customer0_.GENDER as GENDER4_,
customer0_.LAST_NAME as LAST4_4_,
customer0_.referee_id as referee7_4_,
customer0_.name1 as name5_4_,
customer0_.name2 as name6_4_
from
Customer customer0_
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Podemos observar como la query contra la tabla ACCOUNTS se repite 4 veces. Esto se debe a que al hacer el select contra la tabla CUSTOMER, se obtienen 4 resultados (Customers) y por cada uno, se hara un select contra la tabla ACCOUNTS (debido a como se configuro la relacion entre estas dos entidades - fetch=FetchType.EAGER - lectura temprana).
Por otra parte, tambien podemos observar que se hicieron 2 consultas sobre la tabla Referee, ya que los 4 customers, solo tienen como referencia a 2 referees (estas queries se ejecutan ya que como podemos ver, cuando se configuro la relacion uno a uno entre customer y referee, no se indico como debia ser dicha relacion (Ver tutorial JPA I Manejo de Relaciones), por ende, JPA por defecto, a las relaciones uno a uno las hace EAGER - de lectura temprana)
■ Projection Query
List customers = query.getResultList();
for (Object[] customer : customers) {
log.debug("CUSTOMER " + customer[0] + " " + customer[1]);
}
Hibernate:
select
customer0_.FIRST_NAME as col_0_0_,
customer0_.LAST_NAME as col_1_0_
from
Customer customer0_
Como podemos ver en este caso, solo se hara una query SQL simple sin ir a buscar todas las referencias, como si lo hacia la consulta anterior donde tenia que traer todas las columnas de la tabla customer.
■ Conditional Query
Query query = em.createQuery("SELECT c FROM Customer c WHERE c.lastName = 'Last name 4' ");
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_,
customer0_.FIRST_NAME as FIRST2_4_,
customer0_.GENDER as GENDER4_,
customer0_.LAST_NAME as LAST4_4_,
customer0_.referee_id as referee7_4_,
customer0_.name1 as name5_4_,
customer0_.name2 as name6_4_
from
Customer customer0_
where
customer0_.LAST_NAME='Last name 4'
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
En este caso, podemos ver como se hace la consulta para obtener un customer y a su vez un accounts, pero no se obtiene el referee ya que justo este customer, como vemos en las tablas del comienzo de este apartado, no tiene referee asociado.
CUSTOMER_ID FIRST_NAME GENDER LAST_NAME NAME1 NAME2 REFEREE_ID
-------------- ------------- --------- ------------ -------- -------- -------------
4 First name 4 MALE Last name 4 (null) (null) (null)
Query query1 = em.createQuery("SELECT c FROM Customer c WHERE c.lastName = 'Last name 4' and c.firstName = 'First name 4' ");
Ademas del "=", tambien tenemos los siguienets comparadores en JPAQL:
>, >=, <, <=, <> (not equal)
Query query2 = em.createQuery("SELECT c FROM Customer c WHERE c.referee is null");
Query query3 = em.createQuery("SELECT c FROM Customer c WHERE c.referee is not null");
Query query4 = em.createQuery("SELECT a FROM Account a WHERE a.balance BETWEEN 20 and 100 ");
select
account0_.ACCOUNT_ID as ACCOUNT2_3_,
account0_.balance as balance3_,
account0_.CUSOMTER_ID as CUSOMTER6_3_,
account0_.overdraftLimit as overdraf4_3_,
account0_.interestRate as interest5_3_,
account0_.ACCOUNT_TYPE as ACCOUNT1_3_
from
Account account0_
where
account0_.balance between 20 and 100
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_2_,
customer0_.FIRST_NAME as FIRST2_4_2_,
customer0_.GENDER as GENDER4_2_,
customer0_.LAST_NAME as LAST4_4_2_,
customer0_.referee_id as referee7_4_2_,
customer0_.name1 as name5_4_2_,
customer0_.name2 as name6_4_2_,
accounts1_.CUSOMTER_ID as CUSOMTER6_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts1_.balance as balance3_0_,
accounts1_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts1_.overdraftLimit as overdraf4_3_0_,
accounts1_.interestRate as interest5_3_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_3_0_,
referee2_.id as id2_1_,
referee2_.comments as comments2_1_,
referee2_.name as name2_1_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
left outer join
Referee referee2_
on customer0_.referee_id=referee2_.id
where
customer0_.CUSTOMER_ID=?
En el trace del SQL que se ejecuta al lanzar este consulta JPA, podemos apreciar que tambien se ejecuta la consulta para obtener el customer del account (ver que en la relaciones uno-a-uno y muchos-a-uno la lectura de las relaciones es temprana (eager) por defecto si en el mapeo/configuracion de las entidades no indicamos lo contrario - Ver tutorial JPA I Manejo de Relaciones).
Por otra parte, cabe destacar que el between es inclusive, es decir, que es identico a realizar las operacoines >= and <=. El resultado de esta consulta es lo siguiente:
TABLA ACCOUNTS
ACCOUNT_TYPE ACCOUNT_ID BALANCE OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID
--------------- ------------- ---------- ----------------- --------------- --------------
C 2 20 0 (null) 1
S 3 100 (null) 0 1
Query query5 = em.createQuery("SELECT a FROM Account a WHERE a.id IN (1,2)");
El equivalente a esta consulta seria a.id=1 OR a.id=2
Query query6 = em.createQuery("SELECT c FROM Customer c WHERE c.firstName LIKE '_i%a%'");
En este caso esta consulta nos traera todos los registros de la tabla Customer (los 4 registros segun el ejemplo que estamos siguiendo) ya que indica que nos traiga los customers cuyo first name tenga una "i" como segundo caracter y una "a" en algun otro lugar del string
Query query7 = em.createQuery("SELECT c FROM Customer c ORDER BY c.firstName DESC");
Query query8 = em.createQuery("SELECT a FROM Account a ORDER BY a.id ASC, a.balance DESC");
Imagen extraida del libro Java Persistence with Hibernate - Revised Edition fo Hiernate In Action - Christian Bauer and Gavin King - 2007 |
■ Aggregate Function (Funciones de agregacion)
Query query = em.createQuery("SELECT AVG(a.balance) FROM Account a ");
Double avg = (Double) query.getSingleResult();
System.out.println("AVG Balance " + avg);
El valor de esta funcion de agregacion sera un valor numerico, mas especificamente un double.
Segun el ejemplo que estamos siguiendo, esta consulta nos dara un unico resultado que sera
AVG Balance 43.333333333333336
Query query1 = em.createQuery("SELECT MAX(a.balance) FROM Account a ");
Double max = (Double) query1.getSingleResult();
System.out.println("MAX Balance " + max);
Segun el ejemplo que estamos siguiendo, esta consulta nos dara un unico resultado que sera
MAX Balance 100.0
El trace SQL que sera lanzado es el siguiente:
Hibernate:
select
*
from
( select
MAX(account0_.balance) as col_0_0_
from
Account account0_ )
where
rownum <= ?
Lo mismo sera para la funcion MIN
Por otra parte, tanto la funcion de MAX como la de MIN, seran aplicables tanto a valores numericos, como strings, como caracteres o bien como dates.
Query query2 = em.createQuery("SELECT SUM(a.balance) FROM Account a ");
Double sum = (Double) query2.getSingleResult();
System.out.println("SUM Balance " + sum);En este caso, la funcion SUM sumara solamente valores numericos. Esta funcion retornara valores del tipo Long, Double, BigInteger, or BigDecimal
El resultado sera el siguiente:
SUM Balance 130.0
Query query4 = em.createQuery("SELECT COUNT(a) FROM Account a ");
Long count = (Long) query4.getSingleResult();
System.out.println("COUNT " + count);En este caso, la funcion COUNT podra ser aplicada a cualquier propiedad o entidad misma. Esta funcion retornara un valor del tipo Long
El resultado sera el siguiente:
COUNT 3 Query query5 = em.createQuery("SELECT DISTINCT COUNT(c.firstName) FROM Customer c");
Long count1 = (Long) query5.getSingleResult();
System.out.println("COUNT " + count1);
Esta funcion, nos permitira elimiar los valores duplicados en funcion a la propiedad firstName (no se contaran customers mas de una vez si tienen el mismo firstName)
El resultado sera el siguiente:
COUNT 4
NOTA: al aplicar cualquier función de agregacion a cualquier campo con un valor nulo se eliminará independientemente de si la palabra clave distinct es especificada.
■ Group By
Query query = em.createQuery("SELECT a.id, AVG(a.balance) FROM Account a GROUP BY a.id");
List
for (Object[] acc : accounts) {
log.debug("Account ID " + acc[0]+ " " + acc[1]);
}
Tipicamente cuando usamos funciones de agregacion sobre alguno de las propiedades de la entiedad, necesitamos agrupar el resto de las propiedades bajo algun criterio
El traza SQL de esta query es la siguiente
Hibernate:
select
account0_.ACCOUNT_ID as col_0_0_,
AVG(account0_.balance) as col_1_0_
from
Account account0_
group by
account0_.ACCOUNT_ID
Por otra parte, el resultado es el siguiente (luego de iterar mediante un FOR lo que devuelve la consulta)
Account ID 1 10.0
Account ID 2 20.0
Account ID 3 100.0
■ Having
Esta funcion permite agregar condiciones a los grupos que se establezcan mediante la funcion Group By
En primer lugar, hacemos la query sin el Having para que se entienda bien como funciona:
Query query = em.createQuery("SELECT count(c), c.gender FROM Customer c GROUP BY c.gender");
List
for (Object[] customer : customers) {
log.debug("customer " + customer[0] + " " + customer[1]);
}Lo que mostrara esta consulta es lo siguiente (siempre teniendo en cuenta el ejemplo)
customer 3 FEMALE
customer 1 MALE
Ahora agregamos la funcion Having a la query realizada anteriormente y vemos que nos devuelve:
Query query1 = em.createQuery("SELECT count(c), c.gender FROM Customer c GROUP BY c.gender HAVING COUNT(c.gender) > 1");
List
for (Object[] customer : customers1) {
log.debug("customer " + customer[0] + " " + customer[1]);
}
La traza SQL que ejectua esta consulta es la siguiente:
Hibernate:
select
count(customer0_.CUSTOMER_ID) as col_0_0_,
customer0_.GENDER as col_1_0_
from
Customer customer0_
group by
customer0_.GENDER
having
count(customer0_.GENDER)>1
Lo que nos devolvera esta consulta es lo siguiente:
customer 3 FEMALE
Aqui podemos ver que a diferencia de la consulta anterior (donde obtuvimos dos resultados) aqui obtenemos un resultado.
Esto es gracias a la funcion having que nos condiciono el resultado diciendo que solamente devolvera aquellos grupos de generos donde al menos tengan mas de un registro. Y como podemos ver en la tabla Customer, vemos que el genero femenino es el que cumple esta condicion.
■ Queries con relaciones
Query query = em.createQuery("SELECT c.lastName, c.referee.name FROM Customer c ");
List
for (Object[] customer : customers) {
log.debug("customer lastName " + customer[0] + " referee name " + customer[1]);
}La consulta SQL que se ejecuta es la siguiente:
Hibernate:
select
customer0_.LAST_NAME as col_0_0_,
referee1_.name as col_1_0_
from
Customer customer0_,
Referee referee1_
where
customer0_.referee_id=referee1_.id
select
customer0_.LAST_NAME as col_0_0_,
referee1_.name as col_1_0_
from
Customer customer0_,
Referee referee1_
where
customer0_.referee_id=referee1_.id
El resultado de esta consulta es el siguiente:
customer lastName Last name 1 referee name Referee 1
customer lastName Last name 2 referee name Referee 2
customer lastName Last name 3 referee name Referee 1
Otra forma de obtener lo mismo es la siguiente query
Query query1 = em.createQuery("SELECT c.lastName, c.referee FROM Customer c ");
List
for (Object[] customer : customers1) {
log.debug("customer lastName " + customer[0] + " referee name " + ((Referee)customer[1]).getName());
}
Aqui podemos ver que en lugar de obtener el nombre del referee en la clausula del select, obtenemos directamente el obtejo Referee y luego casteamos a Referee y obtenemos el nombre.
Por otra parte, lo que cambia tambien es la consulta SQL que se ejecuta:
Hibernate:
select
customer0_.LAST_NAME as col_0_0_,
customer0_.referee_id as col_1_0_,
referee1_.id as id2_,
referee1_.comments as comments2_,
referee1_.name as name2_
from
Customer customer0_
inner join
Referee referee1_
on customer0_.referee_id=referee1_.id
customer0_.LAST_NAME as col_0_0_,
customer0_.referee_id as col_1_0_,
referee1_.id as id2_,
referee1_.comments as comments2_,
referee1_.name as name2_
from
Customer customer0_
inner join
Referee referee1_
on customer0_.referee_id=referee1_.id
Vemos claramente que se obtienen todas las propiedades de la entidad Referee, mientras que en la consulta anterior solo se obtenia el nombre del Referee.
A. Joins
Al navegar por dos o mas entities, se esta efectuando una operacion de JOIN implicitamente, como vimos anteriormente (ejemplo SELECT c.lastName, c.referee.name FROM Customer c) donde vemos que la notacion por puntos nos simplifica la notacion evitando hacer lo siguiente SELECT c.lastName, r.name FROM Customer c, Referee r WHERE c.referee.id =r.id
Los JOINS implicitos (sea mediante la notacion por puntos o no, como vimos los dos ejemplos anteriores) funcionan perfectamente para las relaciones uno a uno o muchos a uno, ahora bien, para las relaciones uno a muchos o muchos a muchos, podremos navegar por estas relaciones usando el operador JOIN de forma explicita.
B. Inner Joins
Los operadores JOIN y INNER JOIN son sinonimos en JPQL (es decir, la palabra INNER es opcional)
Query query = em.createQuery("SELECT c.lastName, a.balance FROM Customer c INNER JOIN c.accounts a");
List
for (Object[] customer : customers) {
log.debug("customer lastName " + customer[0] + " customer balance " + customer[1]);
log.debug("customer lastName " + customer[0] + " customer balance " + customer[1]);
}
La traza SQL es la siguiente:
Hibernate:
select
customer0_.LAST_NAME as col_0_0_,
accounts1_.balance as col_1_0_
from
Customer customer0_
inner join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
select
customer0_.LAST_NAME as col_0_0_,
accounts1_.balance as col_1_0_
from
Customer customer0_
inner join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
El resultado es el siguiente
customer lastName Last name 1 customer balance 100.0
customer lastName Last name 1 customer balance 20.0
customer lastName Last name 1 customer balance 10.0
IMPORTANTE: En este tipo de consultas, en los casos en donde un customer no tiene una relacion con un account, no va a traer ningun registro. Ya que el JOIN o INNER JOIN funciona de esta manera. En el ejemplo anterior, como podemos ver, solo trajo 3 registros que corresponden a uno de la la tabla Customer que se relaciona con 3 registros de la tabla Accounts. Esto se debe, como bien lo venimos explicando, que un solo registro de la tabla Customer, tiene referencia con la tabla Account mientras que el resto no. - Dicho en otros terminos, existen 3 customers que no tienen ninguna cuenta asociada -
Esta operacion nos devuelve el producto cartesiano entre Customer y Account
Otra forma de escribir la misma query es de la siguiente manera:
Query query1 = em.createQuery("SELECT c.lastName, a.balance FROM Customer c JOIN c.accounts a");
C. Outer Joins
Este es usado cuando no es necesario que el macheo en la condicion de join este presente. Es decir, cuando querramos que nos traigan los Customers independientemente de si tenga o no una Account.
La palabra reservada en JPQL es LEFT OUTER JOIN
Query query2 = em.createQuery("SELECT c.lastName, a.balance FROM Customer c LEFT OUTER JOIN c.accounts a");
List
for (Object[] customer : customers2) {
log.debug("customer lastName " + customer[0] + " customer balance " + customer[1]);
}
La traza SQL es la siguiente:
Hibernate:
select
customer0_.LAST_NAME as col_0_0_,
accounts1_.balance as col_1_0_
from
Customer customer0_
left outer join
Account accounts1_
on customer0_.CUSTOMER_ID=accounts1_.CUSOMTER_ID
El resultado es el siguiente:
customer lastName Last name 1 customer balance 10.0
customer lastName Last name 1 customer balance 20.0
customer lastName Last name 1 customer balance 100.0
customer lastName Last name 4 customer balance null
customer lastName Last name 3 customer balance null
customer lastName Last name 2 customer balance null
Aqui podemos ver claramente una diferencia en los resultados que se obtienen respecto a las 2 consultas anteriores. Lo unico que se hizo fue agregar LEFT OUTER a la condicion del JOIN. Esto permite traer todos los customers mas alla de si tienen o no una, ninguna o varias accounts.
D. Fetch Joins
Esto permite realizar una carga de los datos relacionados de forma temprana (EAGER loading) incluso si se ha especificado una estrategia de lectura tardia (LAZY loading) al definir o configurar las relaciones - Ver tutorial JPA I Manejo de Relaciones
En primer lugar vamos a tirar una simple consulta con INNER JOIN para entender bien la diferencia entre esto y INNER JOIN FETCH
Query query = em.createQuery("SELECT c FROM Customer c INNER JOIN c.addresses");
List
for (Customer customer : customers) {
log.debug("CUSTOMER ID " + customer.getId() + " " + customer.getFirstName() + " " + customer.getLastName());
List addresses = customer.getAddresses();
for (Address address : addresses) {
log.debug("ADDRESS ID " + address.getId());
}
}
log.debug("ADDRESS ID " + address.getId());
}
}
Y aca esta la traza SQL
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_,
customer0_.FIRST_NAME as FIRST2_4_,
customer0_.GENDER as GENDER4_,
customer0_.LAST_NAME as LAST4_4_,
customer0_.referee_id as referee7_4_,
customer0_.name1 as name5_4_,
customer0_.name2 as name6_4_
from
Customer customer0_
inner join
CUSTOMER_ADDRESS addresses1_
on customer0_.CUSTOMER_ID=addresses1_.CUST_ID
inner join
Address address2_
on addresses1_.ADD_ID=address2_.ADDRESS_ID
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_,
customer0_.FIRST_NAME as FIRST2_4_,
customer0_.GENDER as GENDER4_,
customer0_.LAST_NAME as LAST4_4_,
customer0_.referee_id as referee7_4_,
customer0_.name1 as name5_4_,
customer0_.name2 as name6_4_
from
Customer customer0_
inner join
CUSTOMER_ADDRESS addresses1_
on customer0_.CUSTOMER_ID=addresses1_.CUST_ID
inner join
Address address2_
on addresses1_.ADD_ID=address2_.ADDRESS_ID
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
CUSTOMER ID 1 First name 1 Last name 1
Hibernate:
select
addresses0_.CUST_ID as CUST1_1_,
addresses0_.ADD_ID as ADD2_1_,
address1_.ADDRESS_ID as ADDRESS1_5_0_,
address1_.addressLine as addressL2_5_0_,
address1_.country as country5_0_,
address1_.postCode as postCode5_0_
from
CUSTOMER_ADDRESS addresses0_
left outer join
Address address1_
on addresses0_.ADD_ID=address1_.ADDRESS_ID
where
addresses0_.CUST_ID=?
ADDRESS ID 2
ADDRESS ID 0
CUSTOMER ID 1 First name 1 Last name 1
ADDRESS ID 2
ADDRESS ID 0
Como podemos ver, al obtener datos del 1er registro de customer y luego intentar recorrer y loguear los addreess, JPA por detras va a tirar la consulta SQL para obtener dichos valores de la tabla address ya que con la query original no los obtuvimos.
Es decir, podemos ver que cuando despues de loguear el primer registro de customer y luego al intentar acceder a los registros del address, vemos que ahi es cuando tira otra consulta SQL por detras, para obtener dichas addresss. Luego, no tira mas consultas sql porque las cachea para no ir nuevamente a la bd para obtener los mismos regustros.
Por otra parte, vemos que nos repite el customer y eso es correcto ya que la consulta hace el producto cartesiano entre customer y address y como un customer tiene dos address la consulta nos trae dos registros (customer id 1 address id 1 y customer id 1 address id 2) y por cada registro estamos logueando los datos (un customer con dos address)
Por otra parte, vemos que nos repite el customer y eso es correcto ya que la consulta hace el producto cartesiano entre customer y address y como un customer tiene dos address la consulta nos trae dos registros (customer id 1 address id 1 y customer id 1 address id 2) y por cada registro estamos logueando los datos (un customer con dos address)
En resumen, el resultado de la query que estamos logueando es la siguiente
CUSTOMER ID 1 First name 1 Last name 1
ADDRESS ID 2
ADDRESS ID 0
CUSTOMER ID 1 First name 1 Last name 1
ADDRESS ID 2
ADDRESS ID 0
Es decir, como vemos en la tabla CUSTOMER_ADDRESS, existen solo dos resultados donde tenemos que el customer cuyo ID 1 tiene dos addresses
TABLA CUSTOMER_ADDRESS
CUST_ID ADD_ID
---------- --------
1 2
1 0
Ahora vamos a cambiar la query anterior y agregarle la notacion INNER JOIN FETCH y siguiendo el mismo ejemplo (un customer tiene asociado dos address como bien lo indica la tabla CUSTOMER_ADDRESS)
Query query = em.createQuery("SELECT c FROM Customer c INNER JOIN FETCH c.addresses");
List customers = query.getResultList();
for (Customer customer : customers) {
log.debug("CUSTOMER ID " + customer.getId() + " " + customer.getFirstName() + " " + customer.getLastName());
List addresses = customer.getAddresses();
for (Address address : addresses) {
log.debug("ADDRESS ID " + address.getId());
}
}
}
La traza SQL es la siguiente:
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_0_,
address2_.ADDRESS_ID as ADDRESS1_5_1_,
customer0_.FIRST_NAME as FIRST2_4_0_,
customer0_.GENDER as GENDER4_0_,
customer0_.LAST_NAME as LAST4_4_0_,
customer0_.referee_id as referee7_4_0_,
customer0_.name1 as name5_4_0_,
customer0_.name2 as name6_4_0_,
address2_.addressLine as addressL2_5_1_,
address2_.country as country5_1_,
address2_.postCode as postCode5_1_,
addresses1_.CUST_ID as CUST1_0__,
addresses1_.ADD_ID as ADD2_0__
select
customer0_.CUSTOMER_ID as CUSTOMER1_4_0_,
address2_.ADDRESS_ID as ADDRESS1_5_1_,
customer0_.FIRST_NAME as FIRST2_4_0_,
customer0_.GENDER as GENDER4_0_,
customer0_.LAST_NAME as LAST4_4_0_,
customer0_.referee_id as referee7_4_0_,
customer0_.name1 as name5_4_0_,
customer0_.name2 as name6_4_0_,
address2_.addressLine as addressL2_5_1_,
address2_.country as country5_1_,
address2_.postCode as postCode5_1_,
addresses1_.CUST_ID as CUST1_0__,
addresses1_.ADD_ID as ADD2_0__
from
Customer customer0_
inner join
CUSTOMER_ADDRESS addresses1_
on customer0_.CUSTOMER_ID=addresses1_.CUST_ID
inner join
Address address2_
on addresses1_.ADD_ID=address2_.ADDRESS_ID
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Customer customer0_
inner join
CUSTOMER_ADDRESS addresses1_
on customer0_.CUSTOMER_ID=addresses1_.CUST_ID
inner join
Address address2_
on addresses1_.ADD_ID=address2_.ADDRESS_ID
Hibernate:
select
referee0_.id as id2_0_,
referee0_.comments as comments2_0_,
referee0_.name as name2_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER6_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_3_0_,
accounts0_.balance as balance3_0_,
accounts0_.CUSOMTER_ID as CUSOMTER6_3_0_,
accounts0_.overdraftLimit as overdraf4_3_0_,
accounts0_.interestRate as interest5_3_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_3_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Aqui podemos apreciar que no se lanzan las consultas SQL para obtener los address ya que fueron recuperados por la 1er consulta SQL que se lanzo (ver traza sql de esta query de JPA)
Ademas, por cada customer que se obtiene, se obtienen tambien los address y aqui podemos ver una clara diferencia ya que esta consulta no lanza SQLs intermedios como si lo hacia la otra opcion.
Ademas, por cada customer que se obtiene, se obtienen tambien los address y aqui podemos ver una clara diferencia ya que esta consulta no lanza SQLs intermedios como si lo hacia la otra opcion.
Es decir, una vez que se obtuvo todos los customers, luego el recuperar cada address de cada customer, no significara que por detras se lanze una consulta SQL ya que la query JPA resolvio esto de una vez. En cambio, sino usamos la notacion FETCH, al tratar de obtener un address de un customer en particular, por detras lanzara la aplicacion una consutla SQL obteniendo todos los datos referentes al address.
El resultado es el siguiente (igual al ejemplo anterior)
CUSTOMER ID 1 First name 1 Last name 1
ADDRESS ID 2
ADDRESS ID 0
CUSTOMER ID 1 First name 1 Last name 1
ADDRESS ID 2
ADDRESS ID 0
En conclusion, lo que cambia con la notacion FETCH no es el resultado final de los datos que se van a obtener sino la forma (por detras) en que se obtienen los datos. Si a traves de una query obtenemos todo o bien si vamos a ir lanzando a medida que necesitemos las distintas queries para obtener los datos relacionados.
■ Identificadores reservados para el lenguaje de queries JPQL
SELECT
FROM
WHERE
UPDATE
DELETE
JOIN
OUTER
INNER
LEFT
GROUP
BY
HAVING
FETCH
DISTINCT
OBJECT
NULL
TRUE
FALSE
NOT
AND
OR
BETWEEN
LIKE
IN
AS
UNKNOWN
EMPTY
MEMBER
OF
IS
AVG
MAX
MIN
SUM
COUNT
ORDER
BY
ASC
DESC
MOD
UPPER
LOWER
TRIM
POSITION
CHARACTER_LENGTH
CHAR_LENGTH
BIT_LENGTH
CURRENT_TIME
CURRENT_DATE
CURRENT_TIMESTAMP
NEW
EXISTS
ALL
ANY
SOME
Descargas de ejemplos de mapeos JPA y consultas JPQL
Por favor, haga clic en: Descargar ejemplos JPA
Por favor, haga clic en: Descargar ejemplos JPA
Bibliografia
* http://download.oracle.com/docs/cd/E11035_01/kodo41/full/html/ejb3_overview_query.html
* http://openjpa.apache.org/builds/1.2.0/apache-openjpa-1.2.0/docs/manual
* http://openjpa.apache.org/builds/1.2.0/apache-openjpa-1.2.0/docs/manual
* Java Persistence with Hibernate - Revised Edition fo Hiernate In Action - Christian Bauer and Gavin King - 2007
* EJB 3 Developer Guide - A Practical Guide for developers and architects to the Enterprise Java Beans
Standard - Michael Sikora - 2008
gracias por la explicación... Excelente blog
ResponderEliminar