Tabla de Contenidos

Consultas en beans de entidad

PEDRO DELGADO YARZA 2014/02/06 08:53

A la hora de desarrollar aplicaciones, surge la necesidad de realizar consultas a base de datos para recuperar objetos basados en diversos criterios. Dichas consultas en el pasado se han basado netamente en ejecutar consultas nativas SQL de base de datos y mapeando manualmente a clases java, a posteriori, dichos resultados.

Actualmente, el motor de persistencia nos provee de una manera algo más cómoda de recuperar esos datos, ahorrándonos el mapeo manual de valores y simplificando la manera de realizar las consultas JPQL o consultas nativas SQL. No obstante se nos permite seguir realizando dichas búsquedas de la manera anterior, este hecho hace que distingamos entre dos tipos de consultas:

Utilizando cualquiera de los tipos anteriores podemos lanzar nuestras consultas a base de datos, no obstante la configuración necesaria para cada una difiere, como veremos en apartados sucesivos.

Definición de Queries

Una vez que queremos empezar a preparar consultas de base de datos debemos decidir dónde definirlas. En este punto existen dos puntos básicos donde eñnglobarlas:

Consultas declaradas dentro de un bean de entidad

Están contenidas dentro de la clase del bean al que representan y escritas en código HQL. Se disponen en la cabecera de la clase dentro de la anotación @NamedQueries o NamedNativeQueries, indicando cada query con la anotación @NamedQuery o @NamedNativeQuery según si queremos escribirlas en HQL o SLQ.

Un ejemplo con HQL sería:

@Entity
@NamedQueries({
		@NamedQuery(name = "anuncioFiltrado", query = "select an from Anuncio an where an.id like :id and lower(an.descripcion) like :descripcion and lower(an.nombre) like :nombre and  ((:fechaPublicacion is null) OR (an.fechaPublicacion=:fechaPublicacion))"),
		@NamedQuery(name = "totalAnuncioFiltrado", query = "select count(an) from Anuncio an where an.id like :id and lower(an.descripcion) like :descripcion and lower(an.nombre) like :nombre and  ((:fechaPublicacion is null) OR (an.fechaPublicacion=:fechaPublicacion))") })
public class Anuncio implements Serializable {
...
...
}

Como podemos ver, necesitamos hacer uso de dos anotaciones:

De igual manera, si quisiéramos especificarlas en SQL:

@NamedNativeQueries( {
	@NamedNativeQuery(name = "consultaAnunciosNativa", query = "SELECT * FROM ANUNCIO", resultClass=Anuncio.class)
})

Al igual que antes necesitamos hacer uso de dos anotaciones:

Consultas declaradas en el fichero orm.xml

Las consultas anteriores, pueden realizarse registrarse en vez de en el propio bean, en el fichero de configuración orm.xml mediante tags xml.

En el caso de las consultas HQL haremos uso del tag named-query

<named-query name="consultaAnuncios"> 
	<query>select anuncio from Anuncio anuncio</query>
	<hint name="org.hibernate.callable" value="false" />
	<hint name="org.hibernate.readOnly" value="true" />
</named-query>	

Como vemos, las propiedades que requiere son las mismas que son necesarias en el caso anterior, teniendo en el tag <query> la consulta, y en la lista de tags <hint> las propiedades adicionales.

En el caso de que quisiéramos hacer la consulta en SLQ nativo, usaríamos el tag named-native-query procediendo de la siguiente manera:

<named-native-query name="consultaAnunciosNativa" result-class="model.entity.Anuncio"> 
	<query>SELECT * FROM ANUNCIO</query>
	<hint name="org.hibernate.callable" value="false" />
	<hint name="org.hibernate.readOnly" value="true" />
</named-native-query>

Las propiedades adicionales siguen el mismo esquema que el caso anterior.

Consultas que no devuelven todos los campos de un Bean de Entidad

Si queremos realizar una consulta que sólo devuelva un subconjunto de los campos de una tabla, o que devuelva campos de tablas distintas debemos realizar las consultas de dos posibles maneras:

<named-query name="consultaAnunciosParcialHQL">
 	<query>select anuncio.id , anuncio.nombre from Anuncio anuncio</query>
 	<hint name="org.hibernate.callable" value="false" />
	<hint name="org.hibernate.readOnly" value="true" />
</named-query>
<named-native-query name="consultaAnunciosParcial" result-set-mapping="mappingAnuncioColumn"> 
 	<query>SELECT anuncio.ID id, anuncio.NOMBRE nombre FROM FUNDEWEB.ANUNCIO anuncio</query>
 	<hint name="org.hibernate.callable" value="false" />
	<hint name="org.hibernate.readOnly" value="true" />
</named-native-query>
 
<sql-result-set-mapping name="mappingAnuncioColumn" >
	<column-result name="id" />
	<column-result name="nombre" />
</sql-result-set-mapping>
 

También podemos indicar esto en el propio Bean de la forma:

@NamedNativeQueries( {
@NamedNativeQuery(name = "consultaAnunciosParcial", query = "SELECT anuncio.ID id, anuncio.NOMBRE nombre FROM FUNDEWEB.ANUNCIO anuncio", resultSetMapping = "mappingAnuncioColumn")
})
@SqlResultSetMapping(name = "mappingAnuncioColumn", columns = {
		@ColumnResult(name = "id"), @ColumnResult(name = "nombre")
})

Importante: Para utilizar este mapeo en la consulta es necesario que utilicemos alias de las columnas de la consulta que devolverá un ArrayList<Object[]>.

Establecer mapeos para el resultado devuelto por la consulta

Cuando utilizamos named-native-query es posible usar un sql-result-set-mapping para asociar el nombre de las columnas en la tabla a otro diferente en el de las propiedad en el bean de entidad.

Para realizar esta tarea disponemos del tag field-result con las propiedades:

<sql-result-set-mapping name="mappingAnuncio" >
	<entity-result entity-class="model.entity.Anuncio">
		<field-result name="id" column="ID"/>
		<field-result name="nombre" column="NOMBRE"/>
	</entity-result>
</sql-result-set-mapping>

Pasar parámetros a la consulta

Para pasar parámetros a la consulta simplemente escribir el parámetro a pasar con dos puntos antes y posteriormente sustituirlo a la hora de hacer la consulta.

En NamedQuery

@NamedQueries({
		@NamedQuery(name = "anuncioFiltrado", query = "select an from Anuncio an where an.id = :id"  })
public class Anuncio implements Serializable { ... }

En fichero orm.xml

<named-query name="consultaAnunciosFiltro"> 
	<query>select anuncio from Anuncio anuncio where anuncio.id = :id</query>
	<hint name="org.hibernate.callable" value="false" />
	<hint name="org.hibernate.readOnly" value="true" />
</named-query>

Consultas sobre procedimientos

En versiones anteriores de Fundeweb (1.2.x) las consultas se realizaban vía orm.xml, no obstante, tras el desarrollo de Genética las llamadas a funciones y procedimientos han de realizarse pasando por este mecanismo, ya que es más seguro y facilita a los desarrolladores las llamadas, minimizando los errores a la hora de pasar parámetros.

Como ejecutar las consultas

Una vez definido cómo especificamos las consultas vamos a mostrar la manera de llamar a cada una de ellas

//Esta consulta es una query que devuelve directametne los beans de entidad, esta consulta podría ser una NamedQuery o NativeNamedQuery
Query query = entityManager.createQuery("consultaAnuncios");
List<Anuncio> lista = query.getResultList();
 
//Consulta con paso de parámetros
Query query2 = entityManager.createNamedQuery("procedimientoComentario");
query.setParameter("VAR", "juan");
List<Comentario> lista2 = query.getResultList();
 
//Consulta con un mapeo que devuelve una lista de Object[]
Query query3 = entityManager.createNamedQuery("consultaAnunciosParcialHQL");
List<Object[]> lista3 = query.getResultList();

Clase QueryUtils

Es una clase utilidad que nos permite realizar mapeos y transformaciones que las clases de Hibernate no pueden realizar, solo se puede utilizar con consultas nativas SQL. Así pues podemos:

List<Map> lista5 = QueryUtil.getMapFromNamedNativeQuery("consultaAnunciosParcial",entityManager);
for (Map map:lista5){
    BigDecimal id = (BigDecimal) map.get("id");
	String nombre = (String) map.get("nombre");
}
List<AnuncioDto> lista6 = QueryUtil.getDtosFromNamedNativeQuery("consultaAnunciosParcial",entityManager,AnuncioDto.class);