Geek & Roll - Blog Archive » Cuando Hibernate cascade te juega bromas

Cuando Hibernate cascade te juega bromas

Cesar March 23rd, 2010 programacion 7 comentarios

Ah, Hibernate. ¿Que podemos decir? Te atrae con promesas de cerrar la brecha entre lo relacional y el mundo de los objetos, pero luego te deja a que trates de entender por qué demonios no funciona como tu lo esperas. En cierto modo es como C++ que evita que te dispares en el pie, pero cuando lo haces te vuelas la pierna completa.

De lo que quiero hablar en este post es de las operaciones en cascada. Hibernate te permite configurar operaciones en cascada, de tal manera que si así tu lo deseas, cuando creas un objeto que a su vez tiene relación con otro objeto, ambos pueden ser guardados con una sola operación. Por ejemplo:

public class Member(){
    private ExpertiseArea expertiseArea;
}

En este caso, Member tiene una relación uno a uno con ExpertiseArea, y sería deseable que al guardar Member también se guardara ExpertiseArea. Para lograrlo, le decimos a Hibernate cuando debe realizar las operaciones en cascada:

<one-to-one name="expertiseArea"
    class="ExpertiseArea"
    cascade="persist,save-update" />

Con lo anterior le decimos a Hibernate “Cuando ejecutes las operaciones de crear (persist) y de actualizar (save-update) en Member, también hazlo para ExpertiseArea”.

Creamos una prueba para verificar que lo anterior funciona como debería:

	@Test
	public void testMemberRetrieval(){
		Member m = buildMember();
		md.makePersistent(m);
		md.flush();

		Member retrievedMember = md.findById(m.getId(), false);
		assertNotNull("Entity should not be null",retrievedMember);
		Member retrievedMember = md.findById(m.getId(), true);
		assertNotNull("Entity should not be null",retrievedMember);
		assertNotNull("ExpertiseArea should be persistent",
                    retrievedMember.getExpertiseArea());
	}

	private Member buildMember(){
		ExpertiseArea ea = new ExpertiseArea("Singer");
		Member m = new Member("Britney Spears", ea, "Queen of Pop");
		return m;
	}

¿Qué estamos haciendo aquí? Creamos un nuevo Member, le asignamos un ExpertiseArea, y lo guardamos todo en una sola instrucción makePersistent. Como tenemos configuradas las operaciones en cascada, esperamos que se guarden en la base de datos tanto la instancia de Member, como la de ExpertiseArea. Efectivamente, la prueba pasa y podemos revisar en los logs lo que Hibernate hace tras bambalinas:

Hibernate: insert into Member (VERSION, name, tagLine, id) values (?, ?, ?, ?)
Hibernate: insert into ExpertiseArea (VERSION, name, id) values (?, ?, ?)

Pass

No esperaba menos. Se ve claramente como Hibernate inserta a la tabla Member, y después a la tabla ExpertiseArea. Entonces si cambiamos la configuración de las operaciones en cascada a none, la prueba debería de marcar error ¿cierto? Cambiemos pues, la relación de la siguiente manera:

<one-to-one name="expertiseArea"
    class="ExpertiseArea"
    cascade="none" />

Pensemos por un momento en lo que debe de suceder. Como ya no hay operaciones en cascada, cuando guardo un Member, se guarda solo ese Member y Hibernate deja la relación intacta. Hibernate no va a ir y guardar la instancia de ExpertiseArea porque le dijimos precisamente que no lo hiciera. Como no se va a guardar ExpertiseArea, entonces esperamos que la prueba falle, ya que esta va a ser null al recuperar el Member desde Hibernate. Corremos la prueba y esto es lo que vemos:

Hibernate: insert into Member (VERSION, name, tagLine, id) values (?, ?, ?, ?)

WTF

La consola muestra que efectivamente, solo se inserta a la tabla Member ¿Pero la prueba sigue pasando? WTF?!?!?!?! ¿Por qué en nombre de todo lo que es puro y santo, la instancia no es nula? ¿Si no hay operaciones en cascada, y los logs me lo confirman, entonces de donde sale ExpertiseArea?

La respuesta corta es: el objeto Member que Hibernate nos regresa es exactamente el mismo objeto que nosotros declaramos. Ese objeto tiene, en efecto, una instancia no nula de ExpertiseArea. La diferencia es que el objeto ExpertiseArea, aunque no nulo, no es una instancia manejada por Hibernate. En otras palabras, no es un objeto persistente, no se encuentra bajo el mando de la sesión de Hibernate.

¿Y cómo le hacemos para comprobarlo? En vez de probar si la referencia es nula o no, necesitamos probar si el objeto esta siendo manejado por Hibernate o no. Si dejamos que Hibernate maneje los IDs de nuestros objetos, bastará con comprobar si el ID es nulo. Aún mejor es preguntarle a la sesión de Hibernate si contiene al objeto en cuestión.

	@Test
	public void testMemberRetrieval(){
		Member m = buildMember();
		md.makePersistent(m);
		md.flush();

		Member retrievedMember = md.findById(m.getId(), true);
		assertNotNull("Entity should not be null",retrievedMember);
		assertTrue("ExpertiseArea should be persistent",
				md.getSession().contains(retrievedMember.getExpertiseArea()));
	}

Como se puede observar en el código anterior, cambiamos la prueba y le preguntamos a la sesión de Hibernate si contiene el ExpertiseArea del Member que obtuvimos. En este caso, la prueba falla, ya que las operaciones en cascada han sido deshabilitadas.

oh

Si regresamos las operaciones en cascada a su estado original: persist,save-update entonces la prueba pasa, ya que de nuevo Hibernate se encarga de crear y persistir la instancia de ExpertiseArea referida por Member.

Espero le sirva a alguien para no volarse la pierna.

7 Comentarios

jesus

March 30th, 2010

wey nadie te ha comentado, y se me hace gacho asi que, aunque no le entienda y tenga que googlear para encontrar los significados de cada palabra, aqui esta mi comentario :) ah, y gracias por el everlasting flame que no me anda bien con el os 5.0 asi que espero la proxima version

Cesar

March 31st, 2010

Jesus, intenta descargar la version 1.2 para OS 5.Ya esta en las descargas de http://www.cesarolea.com/everlastingflame/

noel

July 26th, 2010

Hola cesar.
gracias por compartir ese tip respecto, al realizar una inserccion en cascade te cuento que estoy recien investigando Hibernate y me parece muy bueno la manera de como te ayuda agilizar tu programacion y la forma sencilla de conectarse a una base de datos. pero como lo explicas tiene muchos detalles de configuracion el que debemos de aprender pero con algo de practica y de gente como tu que comparte sus conocimientos, tengo una duda el cual espero puedas ayudarme, cual es la diferncia entre persist y save, update y merge y para que sirve el flush.
gracias por tu respuesta

Cesar

July 26th, 2010

El metodo normal para guardar un objeto con hibernate es save. Persist fue agregado para ser compatible con la especificacion de EJB3. Si hay diferencias principalmente las garantias que hace persist al momento de guardar un objeto (como no ejecutar un insert fuera de una transaccion) pero para mas informacion puedes leer https://forum.hibernate.org/viewtopic.php?t=951275&highlight=difference+persist+save

Update como su nombre lo indica actualiza los datos de un objeto que ya se encuentra en la base de datos. Merge por el contrario intenta combinar todos los datos de cualquier copia no persistente que se encuentre en memoria de la aplicacion, antes de actualizar el objeto persistente. La diferencia esta en que si intentamos usar update y hay varias copias del mismo objeto, Hibernate no sabe que hacer y tira una exception, cosa que con Merge no sucede siempre y cuando pueda hacer esta combinacion de datos.

Por ultimo, el flush sirve para tomar todos los datos de la sesion y hacer que Hibernate los escriba a la base de datos. Esto sucede automaticamente pero si queremos asegurarnos que lo haga en un momento especifico, usamos flush para forzarlo.

angerrising

December 27th, 2010

Hola estimado,

queria felicitarte por tu genial blog, la verdad es que estoy conociendo hibernate hace muy poco y tengo el problema que al actualizar los datos se me vuelve loco hibernate puesto que a ratos me trae el dato acutalizado y otras veces el dato anterior, será lo que tu dices la solucion?

gracias

Cesar

December 30th, 2010

Si no estas usando transacciones (que es lo recomendado) y no haces el flush manualmente entonces si puede ser. Mi recomendación es que hagas un ejemplo sencillo donde ocurra ese error que mencionas tu, para saber exactamente que es lo que está pasando.

White_King

December 24th, 2011

Yo se este rollo pero no sabia como se configuraba en hibernate, es muy complicado, lo habia echo con JPA pero es bastante complicado aca, como me suscribo a esto.. ¬¬

Haz un comentario:

Es necesario que dejes tu nombre y correo electrónico (no se publicarán).

Si dejas un comentario anónimo, con insultos o ajeno al tema, iremos hasta tu casa y le diremos a tu mamá la cantidad de porno que hay en tu computadora. Si, lo sabemos.