Hibernate 3.1 & Colecciones & Fetch & Lazy
Creación: 16-04-2006
Índice de contenidos
4.3. Consideraciones sobre Join & Lazy
5. Como forzar un join en una consulta HQL
6. Otras consideraciones sobre get() y load()
1. Introducción
Hibernate (http://www.hibernate.org/) es una potente herramienta de persistencia que nos permite mapear clases en una base de datos relacional.
En este tutorial vamos a ver como se comportan ciertas relaciones, y como podemos optimizar las consultas a la base de datos. No pretende ser una guía detallada, sino una introducción a las características de Hibernate en este sentido.
Esta fuera del propósito de este tutorial el especificar la sintaxis para mapear las relaciones entre clases a la base de datos relacional.
Se recomienda al lector leer detalladamente la documentación de Hibernate al respecto. En especial el capítulo 19.1. Fetching strategies.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil Ahtec Signal 259M MXM (Sonoma 2.1 GHz, 2048 MB RAM, 100 GB HD).
- Sistema Operativo: GNU / Linux, Debian Sid (unstable), Kernel 2.6.16, KDE 3.5
- Máquina Virtual Java: JDK 1.5.0_06 de Sun Microsystems
- Eclipse 3.1.2
- Hibernate 3.1.3
- Hibernate Annotations 3.1 beta9
- Hibernate Tools 3.1.0 beta4
3. Colecciones
Por ejemplo cuando tenemos una relación de uno a muchos entre dos clases persistentes A y B, significa que en la clase A tendremos una colección de objetos de la clase B.
En la base de datos esta relación se establece con dos tablas TA y TB, donde en TB hay un campo que es la clave ajena de la tabla TA.
------------+ +-----------+ | TA | -------> | TB | +-----------+ 1 n +-----------+ | idTA (PK) | | idTB (PK) | +-----------+ | idTA (FK) | +-----------+ PK = Clave primaria FK = Clave ajena
Un ejemplo de mapeo para esta relación podría ser:
<hibernate-mapping> <class name="A" table="TA"> <!-- Clave primaria --> <id column="idTA" name="idTA"> <generator class="native"/> </id> <!-- Colección de objetos de B --> <set name="bs"> <key column="idTA" /> <one-to-many class="B" /> </set> </class> </hibernate-mapping> <hibernate-mapping> <class name="B" table="TB"> <!-- Clave primaria --> <id column="idTB" name="idTB"> <generator class="native"/> </id> </class> </hibernate-mapping>
Por defecto en Hibernate 3.1, cuando intentemos recuperar los objetos de A se hará un consulta a la base de datos para recuperar los datos de la tabla TA, pero no se traerá los datos correspondientes de la tabla TB. En su lugar Hibernate proporciona un proxy. Este proxy se encarga de hacer las consultas a la base de datos cuando realmente se consulta la colección de objetos de la clase B.
Es decir, según este esquema, para recuperar los datos de TA y sus objetos relacionados de TB se necesitarían 1+n consultas a la base de datos, donde n es el número de objetos de A (filas en TA).
En muchos casos esta aproximación es adecuada, ya que no se recuperan los datos de TB si no son realmente necesarios. Además esta aproximación evita que si tenemos muchas tablas relacionadas, al hacer una consulta podríamos traernos a memoria prácticamente toda la base de datos.
Desde el punto de vista de la base de datos, esta aproximación es ineficiente, ya que se hacen demasiadas consultas para recuperar una información que podríamos obtener con una sola consulta. Mas adelante veremos que opciones tenemos para modificar este comportamiento.
4. Fetch & Lazy
Para entender mejor como se trae Hibernate la información de la base de datos, podemos hablar de dos conceptos: “cuando” se trae la información de la relación y “como” se la trae (es decir que SQL utiliza).
“Jugar” con estas dos ideas nos permite ajustar el rendimiento.
4.1. Lazy
Al especificar una relación en un mapeo de Hibernate usaremos el atributo “lazy” para definir el “cuando”.
Por defecto lazy es igual a true. Esto significa que la colección no se recupera de la base de datos hasta que se hace alguna operación sobre ella.
Si fijamos lazy a false, cuando se recupere la información de A también se traerá toda la información de los objetos relacionados de B (este era el comportamiento por defecto en la versión 2 de Hibernate).
<set name="bs" lazy="false">
4.2. Fetch
Por otro lado el atributo “fetch” define que SQLs se van a lanzar para recuperar la información.
Por defecto fetch es igual a select. Esto implica que para recuperar cada objeto de B se lanzará una nueva consulta a la base de datos.
Si fijamos el valor de fetch a join, en la misma consulta que se recupera la información de A también se recuperará la información de todos los objetos relacionados de B. Esto se consigue con un left outer join en la sentencia SQL.
<set name="bs" fetch="join">
4.3. Consideraciones sobre Join & Lazy
Nótese que los valores de lazy y fetch son independientes. Por ejemplo si fijamos lazy=”false” y dejamos fetch por defecto (fetch=”select”), implica que cuando se recuperen los datos de TA también se recuperan los de TB, pero se hará con 1+n consultas a la base de datos.
Por el contrario si fijamos el valor de fetch=”join” de forma implícita queda lazy=”false”. No tendría sentido de otra manera ya que al especificar fetch=”join” estamos recuperando toda la información en una sola consulta SQL.
También hay que tener en cuenta que, aunque fijemos el valor de fetch=”join”, sólo se hará una consulta SQL con left outer join si estamos en alguno de los siguientes casos (19.1.2. Tuning fetch strategies):
- Estamos recuperando la información con get() o load()
- Se hace una recuperación implícita por navegar por la asociación.
- Consultas con Crtireia.
- Consultas HQL si en la recuperación se usa subselect.
Es decir si hacemos una consulta HQL del estilo:
List ret = session.createQuery("from A").list();
no se va a utilizar un left outer join, sino que se hará una única consulta para recuperar los valores de TA, y más tarde, cuando se acceda a la colección se hará una nueva consulta para recuperar los valores de TB (estamos en la situación de 1+n consultas).
5. Como forzar un join en una consulta HQL
Independientemente de como hallamos definido el mapeo de la asociación entre A y B, podemos hacer un join explícitamente en una consulta HQL.
Basta con hacer:
List ret = session.createQuery("from
A as a left join fetch a.bs").list();
Nótese la importancia de especificar la palabra reservada “fetch”. Con esto conseguimos que se nos devuelva una lista de objetos de tipo A donde su colección bs ya está cargada con los datos correspondientes de TB.
Si por el contrario no especificamos la palabra reservada “fetch”, se nos devolverá una lista donde cada elemento es a su vez otra lista de dos elementos, el primero (índice 0) contendrá el objeto de tipo A y el segundo (índice 1) contendrá el objeto de tipo B. Pero en el objeto de tipo A la colección bs estará a null. Es decir, estamos recuperando la información pero no se está creando la asociación entre los objetos de A y B.
6. Otras consideraciones sobre get() y load()
Teniendo en cuenta que en este tutorial hemos hablado de cuando se hacen las consultas a la base de datos, vamos a comentar la diferencia entre get() y load().
Cuando hacemos sesison.get() es porque no sabemos si el objeto que estamos solicitando existe o no en la base de datos, por lo tanto Hibernate hace la consulta a la base de datos de forma inmediata. Si no se encontrara el objeto en la base de datos, el método get() devuelve null.
Sin embargo al usar session.load() estamos dejando claro a Hibernate que sabemos con seguridad que el objeto que estamos solicitando sí que existe en la base de datos. En este caso Hibernate no hace la consulta de forma inmediata, sino que espera hasta que accedamos al objeto para hacer la consulta.
7. Conclusiones
Como podéis ver es fundamental conocer bien como funcionan las tecnologías que utilizamos. En el caso de Hibernate hemos visto que dependiendo de como hagamos las cosas puede afectar directamente al rendimiento de la aplicación.
Esto no quiere decir que no debamos usar Hibernate, al contrario, Hibernate nos proporciona grandes beneficios como es la independencia de la base de datos, bajo acoplamiento entre negocio y persistencia, y un desarrollo rápido, ya que con Hibernate podremos cubrir de manera sencilla y rápida el 80 – 90% de la persistencia de
nuestra aplicación. Esto nos permite centrar nuestros esfuerzos en optimizar las consultas que realmente lo merecen (por supuesto siempre después de haber hecho un análisis y haber identificado cual es el cuello de botella).
Desde Autentia (http://www.autentia.com) os animamos a que utilicéis este tipo de tecnologías. Basta ya de reinventar la rueda en cada desarrollo. Debemos construir en función de patrones y estándares, y reutilizando al máximo. Esto nos permite centrarnos en la lógica de negocio, reducir los tiempos de desarrollo, y aumentar la calidad del resultado.
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”
Esta muy bueno el tutorial, pero en el no encontre respuesta a mi problema, ya que al hacer una carga lazy me da el siguiente error:
\\\»org.hibernate.LazyInitializationException: could not initialize proxy – the owning Session was closed\\\»
Es decir que cuando voy a obtener los datos de la coleccion, me dice que no se puede porque ya cerro la session, por favor si el autor o alguien mas me puede ayudar… se lo agradecere eternamente.. Sludos
TitoRobe Han pasado muchos años y a mi me sucedió, lo mismo. Estuve buscando información, de la cual entendí que el error sucede por que tengo las anotaciones en lazy y al cerrar la session esta solo carga los datos los cuales accedí hasta que esta cierre.
Session session = HibernateUtil.getSessionFactory().openSession();
try {
Query q = session
.createQuery(\\\»from OrigenPeriodo op where op.empresa.empresaCodigo=:empresaCodigo and op.periodo.periodoCodigo = :periodoCodigo and op.origen.origenCodigo = :origenCodigo \\\»);
q.setParameter(\\\»empresaCodigo\\\», empresa);
q.setParameter(\\\»origenCodigo\\\», origen);
q.setParameter(\\\»periodoCodigo\\\», periodo);
OrigenPeriodo peri;
q.setMaxResults(1);
peri = (OrigenPeriodo) q.uniqueResult();
//System.out.println(peri.getPeriodo().getPeriodoNombre());
return peri;
} catch (Exception e) {
System.out.println(\\\»Error al cargar los datos \\\» + e.getMessage());
} finally {
if (session != null && session.isOpen()) {
session.close();
}
}
return null;
En mi caso me salia por que en la vista hacia uso del objeto Periodo desde mi objeto OrigenPeriodo.
origenPeriodo.periodo.periodoNombre
AL no haber usado el objeto periodo antes que se cierre la session, este no cargo (Esta en lazy).
Una solución sería poner en EAGER o bien usar en el código anterior, por ejemplo, los otros objetos relacionados -Al inicio le puse el print comentado y todo funcionó-
Disculpen que varios conceptos no los tengo claros, hace 3 semanas empecé a aprender a programar. Me voy leyendo muchos tutoriales de esta web :D.