La idea de esta quinta entrega del tutorial de JPA, es que el lector pueda entender el concepto de queries con nomres o estaticas y queries nativas, como asi tambien, poder ver las consultas SQL que se arrojan por detras para terminar de entender su funcionamiento, como venimos viendo en los anteriores tutoriales.
- Named Queries (consultas con nombres)
- Native Queries (consultas nativas)
- Native Query mapeando el resultado a una entidad
- Named Native Query mapeando el resultado a una entidad
- Scalar Query mapeando el resultado a un listado de array de objeto
- Non-managed Entities Query mapeando el resultado a un listado de POJOs simples
- Transformer
Seguimos como lo venimos haciendo en las entregas anteriores, con los mismos ejemplos de tablas y datos. Para mas detalle de como son la relaciones entre las tablas Ver tutorial Tutorial JPA I - Manejo de relaciones
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 DATEOPENED OVERDRAFTLIMIT INTERESTRATE CUSOMTER_ID ----- ----------- ---------
1 comments 1 Referee 1
2 comments 2 Referee 2
TABLA ACCOUNTS
--------------- ------------- ---------- --------------------- ----------------- --------------- --------------
C 1 10 6/21/2011 12:00:00 AM 0 (null) 1
C 2 20 (null) 0 (null) 1
S 3 100 6/21/2011 12:00:00 AM (null) 0 1
TABLA CUSTOMER_ADDRESS
CUST_ID ADD_ID
---------- --------
1 2
1 0
TABLA ADDRESS
ADDRESS_ID ADDRESSLINE COUNTRY POSTCODE
------------- -------------- ---------- -----------
2 addressLine ARGENTINA 1428
0 addressLine2 BRASIL 1629
■ Named Queries
Las named queries o consultas con nombre son diferentes de las sentencias dinamicas que hemos visto hasta ahora en los anteriores tutoriales, en el sentido en que no pueden cambiar: son leidas y transformadas ensentencias SQL cuando el programa arranca en lugar de cada vez que son ejecutadas.
Las named queries son predefinidas usando la notacion @NamedQuery o @NamedQueries en la definicion de la entidad (clase java)
Este comportamiento estatico las hace mas eficientes y por lo tanto ofrecen un mejor rendimiento.
El alcance de cada named query es el contexto de persistencia actual, por lo tanto el nombre de cada query sera unico bajo el mismo contexto de persistencia
En la entidad Customer agregamos la definicion del namedQuery:
@NamedQuery(name="findSelectedCustomers",
query = "SELECT c FROM Customer c
query = "SELECT c FROM Customer c
WHERE c.firstName LIKE :firstString
AND c.lastName LIKE :secondString"
)
@Entity
public class Customer implements Serializable{
)
@Entity
public class Customer implements Serializable{
....
.....
}Luego armamos la consulta utilizando el metodo createNamedQuery del EntityManager.
Query query = em.createNamedQuery("findSelectedCustomers")
.setParameter("firstString", "First name 1")
.setParameter("secondString", "Last name 1");
List
for (Customer customer : customers) {
log.debug("CUSTOMER " + customer.getFirstName() + " " + customer.getLastName());
}
La traza SQl es la siguiente:
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_,
customer0_.FIRST_NAME as FIRST2_2_,
customer0_.GENDER as GENDER2_,
customer0_.LAST_NAME as LAST4_2_,
customer0_.referee_id as referee7_2_,
customer0_.name1 as name5_2_,
customer0_.name2 as name6_2_
from
Customer customer0_
where
(
customer0_.FIRST_NAME like ?
)
and (
customer0_.LAST_NAME like ?
)
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
El resultado de la ejecucion de la query es lo siguiente:
CUSTOMER First name 1 Last name 1
Ahora vamos a ver como es la anotacion en el caso que querramos tener mas de una named query en la entidad
En la entidad Account agregamos la definicion del namedQuery
La traza SQL es la siguiente:
El resultado de la ejecucion de la consulta es lo siguiente:
En la entidad Account agregamos la definicion del namedQuery
@NamedQueries({
@NamedQuery(name="findAccountsWithDateOpened",
query = "SELECT a FROM Account a
@NamedQuery(name="findAccountsWithDateOpened",
query = "SELECT a FROM Account a
WHERE a.dateOpened IS NOT NULL"
),
@NamedQuery(name="findAccountsWithOutDateOpened",
query = "SELECT a FROM Account a
),
@NamedQuery(name="findAccountsWithOutDateOpened",
query = "SELECT a FROM Account a
HERE a.dateOpened IS NULL"
)
})
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="ACCOUNT_TYPE")
public abstract class Account implements Serializable{
)
})
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="ACCOUNT_TYPE")
public abstract class Account implements Serializable{
.......
.......
}
Query query1 = em.createNamedQuery("findAccountsWithDateOpened");
List accounts = query1.getResultList();
for (Account a : accounts) {
log.debug("ACCOUNT ID: " + a.getId() + " dateOpened " + a.getDateOpened());
}
List
for (Account a : accounts) {
log.debug("ACCOUNT ID: " + a.getId() + " dateOpened " + a.getDateOpened());
}
Hibernate:
select
account0_.ACCOUNT_ID as ACCOUNT2_1_,
account0_.balance as balance1_,
account0_.CUSOMTER_ID as CUSOMTER7_1_,
account0_.dateOpened as dateOpened1_,
account0_.overdraftLimit as overdraf5_1_,
account0_.interestRate as interest6_1_,
account0_.ACCOUNT_TYPE as ACCOUNT1_1_
from
Account account0_
where
account0_.dateOpened is not null
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_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=?
select
account0_.ACCOUNT_ID as ACCOUNT2_1_,
account0_.balance as balance1_,
account0_.CUSOMTER_ID as CUSOMTER7_1_,
account0_.dateOpened as dateOpened1_,
account0_.overdraftLimit as overdraf5_1_,
account0_.interestRate as interest6_1_,
account0_.ACCOUNT_TYPE as ACCOUNT1_1_
from
Account account0_
where
account0_.dateOpened is not null
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_2_2_,
customer0_.FIRST_NAME as FIRST2_2_2_,
customer0_.GENDER as GENDER2_2_,
customer0_.LAST_NAME as LAST4_2_2_,
customer0_.referee_id as referee7_2_2_,
customer0_.name1 as name5_2_2_,
customer0_.name2 as name6_2_2_,
accounts1_.CUSOMTER_ID as CUSOMTER7_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_4_,
accounts1_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts1_.balance as balance1_0_,
accounts1_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts1_.dateOpened as dateOpened1_0_,
accounts1_.overdraftLimit as overdraf5_1_0_,
accounts1_.interestRate as interest6_1_0_,
accounts1_.ACCOUNT_TYPE as ACCOUNT1_1_0_,
referee2_.id as id0_1_,
referee2_.comments as comments0_1_,
referee2_.name as name0_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=?
ACCOUNT ID: 1 dateOpened 2011-06-23
ACCOUNT ID: 3 dateOpened 2011-06-23
ACCOUNT ID: 3 dateOpened 2011-06-23
■ Native Queries
Este tipo de consultas requiere una sentencia SQL en lugar de una sentencia JPQL.
Las consultas nativas SQL pueden ser definidas de manera estatica como las consultas con nombre (como vimos en el apartado anterior), obteniendo los mismos beneficios de eficiencia y rendimiento. A estas se las llaman Named Native Queries.
La principal desventaja que tenemos al usar directametne SQL nativo es que se pierde la portabilidad sobre la cual fue pensado JPA. En cambio la ventaja que tiene sobre JDBC directo es que los resultados de las consultas SQL nativas pueden ser retornados como entidades
Los resultados se podrian mapear en:
- Objetos del tipo entidades
- Valores escalares (en un listado de array de objetos)
- Objetos POJOs simples (no entidades)
Para desarrollar queries nativas tendremos que armar la consulta utilizando el metodo createNativeQuery() de la interface javax.persistence.EntityManager (JPA) o bien utilizando el metodo createSQLQuery() de la interface org.hibernate.Session (que pertenece a la implementacion del ORM que en estos ejemplos es Hibernate)
Native Query (Query Nativa) mapeando el resultado en un objeto del tipo entidad
Tener en cuenta que en el SELECT tendremos que poner todos los campos de la tabla o en su defecto colocar asterisco (*). Ejemplo: SELECT * FROM CUSTOMER.
Un dato importante es que el resultado si o si tendra que ser mapeado a un objeto del tipo entidad y no a un simple POJO en esta forma de desarrollar queries nativas
Native Query (Query Nativa) mapeando el resultado en un objeto del tipo entidad
Tener en cuenta que en el SELECT tendremos que poner todos los campos de la tabla o en su defecto colocar asterisco (*). Ejemplo: SELECT * FROM CUSTOMER.
Un dato importante es que el resultado si o si tendra que ser mapeado a un objeto del tipo entidad y no a un simple POJO en esta forma de desarrollar queries nativas
Query query = em.createNativeQuery("SELECT CUSTOMER_ID, " +
" FIRST_NAME, " +
" LAST_NAME, " +
" GENDER, " +
" NAME1, " +
" NAME2, " +
" REFEREE_ID " +
" FROM CUSTOMER", Customer.class);
" FIRST_NAME, " +
" LAST_NAME, " +
" GENDER, " +
" NAME1, " +
" NAME2, " +
" REFEREE_ID " +
" FROM CUSTOMER", Customer.class);
List customers = query.getResultList();
for (Customer customer : customers) {
log.debug("CUSTOMER " + customer.getFirstName() + " " + customer.getLastName());
}La traza SQL es la siguiente:
Hibernate:
SELECT
CUSTOMER_ID,
FIRST_NAME,
LAST_NAME,
GENDER,
NAME1,
NAME2,
REFEREE_ID
FROM
CUSTOMER
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
referee0_.id as id0_0_,
referee0_.comments as comments0_0_,
referee0_.name as name0_0_
from
Referee referee0_
where
referee0_.id=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
Hibernate:
select
accounts0_.CUSOMTER_ID as CUSOMTER7_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_,
accounts0_.ACCOUNT_ID as ACCOUNT2_1_0_,
accounts0_.balance as balance1_0_,
accounts0_.CUSOMTER_ID as CUSOMTER7_1_0_,
accounts0_.dateOpened as dateOpened1_0_,
accounts0_.overdraftLimit as overdraf5_1_0_,
accounts0_.interestRate as interest6_1_0_,
accounts0_.ACCOUNT_TYPE as ACCOUNT1_1_0_
from
Account accounts0_
where
accounts0_.CUSOMTER_ID=?
El resultado de la ejecucion es lo siguiente:
CUSTOMER First name 1 Last name 1
CUSTOMER First name 2 Last name 2CUSTOMER First name 3 Last name 3
CUSTOMER First name 4 Last name 4
Named Native Queries mapeando el resultado en un objeto del tipo entidad
En la entidad Address armamos el named query como lo venimos haciendo en los ejemplos anteriores con la unica diferencia que la query no esta hecha en JPQL sino que esta en SQL. Luego utilizamos la propiedad resultClass para saber contra que clase mapeara el resultado de esta named native query.
Tener en cuenta que en el SELECT tendremos que poner todos los campos de la tabla o en su defecto colocar asterisco (*). Ejemplo: SELECT * FROM ADDRESS.
Un dato importante es que el resultado si o si tendra que ser mapeado a un objeto del tipo entidad y no a un simple POJO en esta forma de desarrollar queries nativas.
@NamedNativeQuery(name="findAllAddress",
query = "SELECT ADDRESS_ID,
ADDRESSLINE,
COUNTRY,
POSTCODE
FROM ADDRESS",
resultClass=Address.class
)
@Entity
public class Address implements Serializable{
)
@Entity
public class Address implements Serializable{
......
......
}
Query query1 = em.createNamedQuery("findAllAddress");
List address = query1.getResultList(); List
for (Address a : address) {
log.debug("ADDRESS ID " + a.getId() + " ADD LINE " + a.getAddressLine());
}
La traza SQL es la siguiente:
Hibernate:
SELECT
ADDRESS_ID,
ADDRESSLINE,
COUNTRY,
POSTCODE
FROM
ADDRESS
ADDRESS_ID,
ADDRESSLINE,
COUNTRY,
POSTCODE
FROM
ADDRESS
El resultado de la query es lo siguiente:
ADDRESS ID 2 ADD LINE addressLine
ADDRESS ID 0 ADD LINE addressLine2
Scalar Queries (consultas escalares) mapeando el resultado a un listado de array de objetos (List<Object[]>)
Importante: Para utilizar valores escalares con queries nativas (Ojo q no es lo mismo que la proyeccion en JPQL ver Tutorial JPA III - HQL y JPA QL - Projection Query), en JPA se debera utilizar la anotacion @SqlResultSetMapping. Esta anotacion y los ejemplos pertinentes no sera vistos en este tutorial por considerarlos pocos practicos y muy engorrosos.
Ahora bien, para prceder a explicar este tema se utilizara directamente la implementacion del ORM elegido y en este caso, el ORM elegido es Hibernate.
Es decir, en lugar de utilizar la interfece javax.persistence.EntityManager (como lo venimos haciendo en el resto de los ejemplos) vamos a usar la interface org.hibernate.Session para llevar a cabo dichos ejemplos
Alternativa 1: La consulta nativa SQL más básica es obtener una lista de valores escalares
Session session = (Session) em.getDelegate();
SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM CUSTOMER");
List<Object[]> list
ADDRESS ID 0 ADD LINE addressLine2
Scalar Queries (consultas escalares) mapeando el resultado a un listado de array de objetos (List<Object[]>)
Importante: Para utilizar valores escalares con queries nativas (Ojo q no es lo mismo que la proyeccion en JPQL ver Tutorial JPA III - HQL y JPA QL - Projection Query), en JPA se debera utilizar la anotacion @SqlResultSetMapping. Esta anotacion y los ejemplos pertinentes no sera vistos en este tutorial por considerarlos pocos practicos y muy engorrosos.
Ahora bien, para prceder a explicar este tema se utilizara directamente la implementacion del ORM elegido y en este caso, el ORM elegido es Hibernate.
Es decir, en lugar de utilizar la interfece javax.persistence.EntityManager (como lo venimos haciendo en el resto de los ejemplos) vamos a usar la interface org.hibernate.Session para llevar a cabo dichos ejemplos
Alternativa 1: La consulta nativa SQL más básica es obtener una lista de valores escalares
Session session = (Session) em.getDelegate();
SQLQuery sqlQuery = session.createSQLQuery("SELECT * FROM CUSTOMER");
List<Object[]> list
for (Object[] object : list) {
log.debug("CUSTOMER ID: " + object[0] + " FIRSTNAME: " + object[1]);
}
Esta consulta retornara una lista de Array de objetos con los valores escarales de cada una de las columnas de la tabla Customer. Hibernate utilizara ResultSetMetadata para deducir el orden y el tipo de cada valores escalar retornado.
Esta consulta retornara una lista de Array de objetos con los valores escarales de cada una de las columnas de la tabla Customer. Hibernate utilizara ResultSetMetadata para deducir el orden y el tipo de cada valores escalar retornado.
Alternativa 2: Obtener los valores escalares explicitando el tipo
Para evitar que Hibernate deduzca estos valores escalares o simplemente para ser mas explicitos a la hora de definir de que tipo sera los valores que sera retornados por la cosnutla es que utilzamos el metodo addScalar() de la interface SqlQuery
SQLQuery sqlQuery1 = session.createSQLQuery("SELECT * FROM CUSTOMER");
sqlQuery1.addScalar("CUSTOMER_ID", Hibernate.INTEGER);
sqlQuery1.addScalar("FIRST_NAME", Hibernate.STRING);
sqlQuery1.addScalar("GENDER", Hibernate.STRING);
sqlQuery1.addScalar("LAST_NAME", Hibernate.STRING);
sqlQuery1.addScalar("NAME1", Hibernate.STRING);
sqlQuery1.addScalar("NAME2", Hibernate.STRING);
sqlQuery1.addScalar("REFEREE_ID", Hibernate.INTEGER);
List<Object[]> list1
log.debug("CUSTOMER ID: " + object[0] + " FIRSTNAME: " + object[1]
);
);
La traza SQL para ambas alternativa es la siguiente:
Hibernate:
SELECT
*
FROM
CUSTOMER
El resultado es el siguiente:
CUSTOMER ID: 1 FIRSTNAME: First name 1
CUSTOMER ID: 2 FIRSTNAME: First name 2
CUSTOMER ID: 3 FIRSTNAME: First name 3
CUSTOMER ID: 4 FIRSTNAME: First name 4
Non-managed entities mapeando el resultado a un listado de objetos POJOs simples
Es posible una transformacion a las consultas SQL nativas para retornar un listado de objetos POJOs que no son manejados por el contexto de persistencia de hiberante (no son objetos del tipo entidad) en lugar de retornar un listado de array de objetos (como el caso anterior).
Aqui tambien se veran los ejemplos utilizando directamente la interface de Hibernate "Session".
Session session = (Session) em.getDelegate();
SQLQuery sqlQuery2 = session.createSQLQuery("SELECT FIRST_NAME as firstName,
" + LAST_NAME as lastName " +
" FROM CUSTOMER");
sqlQuery2.setResultTransformer(Transformers.aliasToBean(CustomerDef.class));
sqlQuery2.addScalar("firstName");
sqlQuery2.addScalar("lastName");
List<CustomerDef> list2 = sqlQuery2.list();
for (CustomerDef customerDef : list2) {
log.debug("CUSTOMER First Name: " + customerDef.getFirstName() + " Last Name: " + customerDef.getLastName());
}
Importante: El metodo addScalar() es requerido el cual hace el mapeo con el nombre de la propiedad del POJO ya que la consulta retorna el nombre de las columnas en mayuscula (ej "FIRSTNAME") y si no utilizamos dicho metodo no se podran bindear los valores del SELECT con las propiedades del POJO.
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
Agradecemos el apoyo sobre estos temas a Sergio Jose Olijavetzky dada su larga trayectoria y experiencia en JPA
ResponderEliminar