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:

  • NamedQuery: Consultas escritas en el lenguaje proporcionado por el motor de persistencia, en el caso de Hibernate el HQL.
  • NamedNativeQuery: Consultas escritas en lenguaje SQL nativo.

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.

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: Estas consultas se declaran en la propia clase del bean de entidad y tienen como requisito que los objetos que devuelva sean del tipo del bean.
  • Consultas declaradas en el fichero orm.xml: Estas consultas se declaran en un fichero de configuración orm.xml y pueden devolver o no beans de entidad. En este caso no se aplica ninguna restrucción concreta.

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:

  • NamedQueries: Especifica una lista separada por comas de consultas HQL.
  • NamedQuery: Representa una consulta HQL. Sus propiedades son:
    • name: Nombre de la consulta. Este nombre lo utilizaremos a la hora de buscarla para lanzarla.
    • query: Consulta en HQL.
    • hints: Propiedades adicionales que queramos darle a la consulta.

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:

  • NamedNativeQueries: Especifica una lista separada por comas de consultas SQL.
  • NamedNativeQuery: Representa una consulta SQL. Sus propiedades son:
    • name: Nombre de la consulta. Este nombre lo utilizaremos a la hora de buscarla para lanzarla.
    • query: Consulta en SQL.
    • resultClass: Clase a la que se ha de mapear el resultado de la consulta.

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.

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:

  • Escribir una named-query que devuelva sólo los campos que queremos. El resultado de esta consulta devolverá un ArrayList<Object[]> donde cada elemento del ArrayList será una fila de la consulta (un par id/nombre), contenidos dentro de un Object[] que contendrá los diferentes valores de la fila. Para acceder a estos valores lo haremos en forma de array Object[0], Object[1], …
<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>
  • Escribir una named-native-query (SQL) que devuelva dos campos. (id y nombre) y especificar (obligatorio) un result-set-mapping a columnas, como mostramos a continuación.
<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[]>.

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:

  • name: Nombre que quiero que tenga la columna resultado.
  • column: Nombre de la columna real que se devuelve tras la query.
<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>

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>

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.

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();

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:

  • Mapear el resultado de una Query en un Map formado por “nombre propiedad” y valor, en vez de la lista de Object[] que devuelve por defecto el motor de persistencia.
List<Map> lista5 = QueryUtil.getMapFromNamedNativeQuery("consultaAnunciosParcial",entityManager);
for (Map map:lista5){
    BigDecimal id = (BigDecimal) map.get("id");
	String nombre = (String) map.get("nombre");
}
  • Mapear directamente al objeto destino que queremos tener. Para ello es imprescindible que cada atributo de la clase de mapeo destino tenga su get y su set.
List<AnuncioDto> lista6 = QueryUtil.getDtosFromNamedNativeQuery("consultaAnunciosParcial",entityManager,AnuncioDto.class);
  • fdw2.0/fundeweb2.0/gt/consultas_y_named_queryes_en_beans_de_entidad.txt
  • Última modificación: 07/11/2017 10:46
  • (editor externo)