Hibernate y Joins con la clase Criteria

3
93289

Creación: 20-06-2007

Índice de contenidos

1. Introducción

Dentro de Hibernate (http://www.hibernate.org/) podemos encontrar la interfaz Criteria
(org.hibernate.Criteria). Esta interfaz nos permite especificar consultas programáticamente (en base a clases y métodos de estas clases) sobre nuestras entidades definiendo un conjunto de restricciones.

El uso de la clase Criteria puede ser muy conveniente cuando tenemos que componer consultas de forma dinámica, por ejemplo con las típicas pantallas de búsqueda, donde el usuario introduce una serie de criterios. En estos casos en vez de ir componiendo un String con el HQL en función de los datos introducidos por el usuario, puede resultar más cómodo usar la clase Criteria.

En este tutorial vamos a ver como hacer «joins» entre entidades relacionadas, y las implicaciones que esto puede tener.

Este tutorial se basa en el tutorial
http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=hibernateAnnotations.
El tutorial esta desarrollado con JUnit 4 (http://www.junit.org/), la sesión de Hibernate se abre y se cierra por cada método de test, y todas las operaciones se hacen dentro de una transacción.

Aquí podéis encontrar un archivo comprimido con todo el código. Es un proyecto de Maven (http://maven.apache.org/), así que las dependencias necesarias para compilar y ejecutar se os bajarán de Internet.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Asus G1 (Core 2 Duo a 2.1 GHz, 2048 MB RAM, 120 GB HD).
  • Sistema Operativo: GNU / Linux, Debian (unstable), Kernel 2.6.21, KDE 3.5
  • Máquina Virtual Java: JDK 1.6.0-b105 de Sun Microsystems
  • Eclipse 3.2.2
  • Hibernate 3.2.2.ga
  • Hibernate Tools 3.2.0 Beta9
  • MySql 5.0.41-2
  • JUnit 4.3.1
  • Maven 2.0.7

3. Usando Criteria.createCriteria()

En la primera aproximación vamos a usar el siguiente código.

Si observamos el resultado volcado por prettyPrinter (también se están volcando las consultas que hace Hibernate, ya que en el fichero de configuración hibernate.cfg.xml
tenemos activa la opción <property name=«show_sql»>true</property>):

Podemos observar como, cada vez que se entra en una relación se vuelve a realizar una consulta contra la base de datos para recuperar la información. Esto se debe a que las relaciones están configuradas con lazy=true (ver tutorial
http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=hibernateJoin),
y cada vez que se navega por la relación (getSubcampaigns() o getBudgets()) se vuelve a hacer una consulta para recuperar la información.

Pero entonces ¿para que sirve el método createCriteria()? Bien, el método
createCriteria() sirve para definir nuevas restricciones sobre las clases de la relación, pero el uso de createCriteria() no quiere decir que se recuperen todos los datos de la relación, de hecho no se recuperará ninguno.

De hecho, en nuestro ejemplo, como no especificamos ninguna restricción sobre ninguna de las clases relacionadas, podríamos haber quitado el createCriteria("subcampaigns")
y el createCriteria("budgets").

4. Usando Criteria.setFetchMode

Este segundo ejemplo es muy similar al anterior, salvo que hemos cambiado los createCriteria() por setFetchMode(). Cabe destacar en este caso que para hacer referencia los presupuestos lo hacemos a través de «subcampaigns.budgets«, es decir, los setFetchMode() siempre se refieren a la entidad definida en el último createCriteria (en nuestro ejemplo session.createCriteria(Campaign.class)):

Si ahora observamos la salida, veremos:

Esta vez parece que si está funcionando correctamente, se está haciendo una única consulta al principio, y luego se navega por todas las relaciones, pero esta vez ya no hace nuevas consultar a la base de datos. Con setFetchMode() estamos definiendo como se quieren recuperar los objetos de las relaciones, ignorando la configuración que tengamos en los atributos lazy o fetch.

Sin embargo no podemos cantar victoria tan rápido. Si os habéis fijado en ambos ejemplos usamos
setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY),
con esto estamos indicando a Hibernate que queremos que nos devuelva sólo las entidades diferentes de la entidad raíz (Campaign.class). El problema que tiene esto es que la búsqueda de entidades diferentes se hace en memoria, es decir, Hibernate en vez de usar un «distinct» en la sentencia SQL, se trae todos los resultado para luego hacer la búsqueda en memoria. Esto puede ser muy desaconsejable, ya que se podría dar el caso de traernos multitud de resultados cuando sólo un pequeño porcentaje son realmente
diferentes.

Solucionar esto con la clase Criteria no es tarea fácil. En el próximo ejemplo se ve una aproximación.

5. Usando proyecciones

Con la clase Projection, podemos definir consultar escalares, es decir, consultas donde no nos traemos entidades enteras, sino que definimos uno a uno los distintos campos que queremos recuperar de la base de datos

Si vemos la salida:

*** projection ***

Podemos observar como sólo se ha traído la entidad raíz (Campaign.class), y como además las relaciones no son navegables!!! Es decir, aunque hemos usado un Transformer para convertir las columnas sueltas en una entidad correspondiente, estos objetos no se ven como objetos de Hibernate, por lo que las operaciones que hacemos sobre ellos son ignoradas por Hibernate.

En el foro http://forum.hibernate.org/viewtopic.php?t=941669, podemos encontrar más información sobre el uso de Projection, pero veremos que su uso es complicado, y rara vez conseguiremos obtener lo que realmente buscamos.

6. Usando HQL

Parece que en esta ocasión, cuando queremos hacer joins y navegar por las relaciones sin que Hibernate vuelva a lanzar consultas contra la base de datos, la clase Criteria se nos queda un poco corta. En este último ejemplo mostramos como hacerlo con HQL:

Si inspeccionamos la salida nos encontraremos con:

Como podemos ver es justo lo que necesitábamos. Con una sola consulta a la base de datos, obtenemos las entidades con las relaciones debidamente rellenas, y el distinct se está haciendo en la base de datos, y no en memoria.

7. Conclusiones

Ya hemos visto varias capacidades de Hibernate, y la clase Criteria es otra de ellas. También hemos comentado las ventajas de la clase Criteria y en que ocasiones puede ser conveniente su uso. Pero siempre tenemos que tener presente el antipatrón del Martillo de Oro (Golde Hammer): Cuando tu única herramienta es un martillo todo parecen clavos.

Es decir, no debemos querer solucionar todos los problemas de la misma manera. Hay que intentar elegir la solución más adecuada para cada ocasión, y en esta ocasión se ha
demostrado que la clase Criteria no es la solución más adecuada, sino que es mucho más sencillo y efectivo usar HQL.

8. Sobre el autor

Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software)

Socio fundador de Autentia (Formación, Consultoría, Desarrollo de sistemas transaccionales)

mailto:alejandropg@autentia.com

Autentia Real Business Solutions S.L. – «Soporte a Desarrollo»

http://www.autentia.com

 

3 Comentarios

  1. Complejillo de leer, requiere alto nivel de procesamiento matemático.
    Por otra parte muy agradecido Alejando y encantado de seguir viéndote haciendo movidas.

  2. Excelente explicación, tenemos ese problema que toooodo lo queremos resolver con criteria y a veces nos complicamos innecesariamente, cuando hay caminos mucho más faciles. Tenemos multiplies resultados por el mal uso de projections y criteria en aplicaciones viejitas, asi que será momento de actualizarlas, Gracias por tus aportes.

Dejar respuesta

Please enter your comment!
Please enter your name here