Herramientas de usuario

Herramientas del sitio


unidades:06_objetos_validaciones:01_trabajando_objetos

Trabajando con Objetos

Este tema va a ser un poco más teórico que los que hemos visto hasta ahora,pero por suerte no va a ser muy necesario excepto en casos muy concretos del uso de Hibernate.

Quizás no nos hayamos parado a pensar cómo funciona Hibernate internamente, al fin y al cabo , Hibernate nos debe abstraer de todas sus interioridades. Desgraciadamente sin conocer algo de cómo funciona internamente nos podemos encontrar con errores en los que no tengamos ni idea de por qué se producen, por lo tanto, con ésta carga teórica, este tema pretende ayudar a explicar esos errores inexplicables.

Durante todo el curso no se ha hecho mención alguna a que los objetos que persistimos con Hibernate tienen un estado. Aparentemente no tiene mucho sentido ya que no hay ninguna propiedad estado en nuestras clases.Sin embargo el siguiente diagrama muestra los estados un objeto al respecto de Hibernate:

Veamos ahora en qué consisten cada uno de los 4 estados:

  • Transitorio (Transient): Un objeto estará en estado Transitorio cuando acabe de ser creado en Java mediante el operador new. Es decir cuando esté recién creado por nosotros. Este estado tiene la característica de que hibernate no sabe nada de nuestro objeto. Quizás el objeto ya este guardado en base de datos 1) o sea nuevo y tengamos que insertarlo.
  • Persistido (Persistent): Un objeto estará en estado Persistido cuando ya está guardado en la base de datos y además Hibernate también es consciente de ello. Fíjese la diferencia con el estado anterior en el que el objeto podía estar persistido pero Hibernate lo desconocía. Hibernate en ese caso guarda el objeto en la cache interna que posee. También es importante destacar que para una misma fila de la base de datos sólo puede haber un único objeto en estado Persistido.
  • Despegado (Detached): Este estado es similar al estado Transitorio sólo que se produce cuando cerramos la sesión mediante Session.close() o llamamos al método Session.evict(Object objeto) para el objeto que queremos pasar a este estado. En ese caso Hibernate vuelve a olvidar en qué estado se encontraban los objetos borrándolo de su cache interna.
  • Removido (Removed): A este estado pasan los objetos que se han borrado de la base de datos mediante el método delete().

Métodos de Session

En el tema Cascade ya se habló de varios métodos que tiene la clase org.hibernate.Session.Pasemos ahora a explicarlos desde el punto de vista de los estados internos de Hibernate.

Session.evict(Object objeto)

El método Session.evict(Object objeto) hace que un objeto en estado Persistido pase al estado Despegado y lo borre de su cache.

1
Session session = sessionFactory.openSession();
Profesor profesor =(Profesor)session.get(Profesor.class,1001);
session.evict(profesor);
session.close();

Session.merge(Object objeto)

El método Session.merge(Object objeto) hace que cualquier objeto pase a estar en el estado Persistido. En caso de que el objeto está en la base de datos , lo leerá de ella. Si el objeto es nuevo , lo marcará como pendiente de guardar en la base de datos.

Este método se suele usar cuando tenemos un objeto que no estaba controlado por Hibernate para que vuelva a estar bajo su control.

Es importante destacar que el objeto que le pasamos como parámetro no tendrá el estado Persistido sino que es el objeto que nos devuelve el que estará en estado Persistido.

1
Session session = sessionFactory.openSession();
Profesor profesor = new Profesor(1001,"LLUIS", "GOMIS", "MARTINEZ");
 
Profesor profesor2 = (Profesor) session.merge(profesor);
System.out.println("¿Son iguales los objeto profesor y profesor2? " + (profesor == profesor2));
 
Profesor profesor3 = (Profesor) session.merge(profesor2);
System.out.println("¿Son iguales los objeto profesor2 y profesor3? " + (profesor2 == profesor3));
 
session.close();
  • En la línea 2 creamos el objeto profesor en estado Transitorio, aunque dicho profesor sí que existe en la base de datos.
  • La línea 4 hace que se retorne una nueva referencia a un objeto de la clase Profesor cuyo Id=1001 y cuyo estado será Persistido.
  • La línea 5 muestra si son iguales los objetos profesor y profesor2 siendo el resultado false, ya que profesor era desconocido para Hibernate, por lo que ha tenido que crear un nuevo objeto y leerlo de la base de datos.
  • En la línea 7 volvemos a hacer un merge del objeto profesor3 que ya se encuentra en estado Persistido.
  • El resultado de la línea 8 ahora es true ya que si hacemos un merge de un objeto que ya está en estado Persistido se retorna el mismo objeto.

Session.refresh(Object objeto)

El método Session.refresh(Object objeto) es similar a Session.merge(Object objeto) pero sólo se puede usar si el objeto ya existe en la base de datos.

Se suele usar para recargar el estado del objeto porque ha sido modificado en la base de datos por algún trigger o similar.

1
Session session = sessionFactory.openSession();
Profesor profesor = new Profesor(1001,"LLUIS", "GOMIS", "MARTINEZ");
session.refresh(profesor);
session.close();

Si el objeto no existe en la base de datos se producirá la excepción

org.hibernate.UnresolvableObjectException: No row with the given identifier exists: [ejemplo01.Profesor#-1]

LockRequest.lock(Object objeto)

El método LockRequest.lock(Object objeto) no modifica el estado interno del objeto pero sí que permite hacer un SELECT … FOR UPDATE contra la base de datos.

Veamos un ejemplo.

1
Session session = sessionFactory.openSession();
Profesor profesor =(Profesor)session.get(Profesor.class,1001);
session.buildLockRequest(LockOptions.UPGRADE).lock(profesor);
session.close();
  • En la línea 2 leemos el objeto
  • En la línea 3 lo bloqueamos en la base de datos.

Las SQL que se generan son las siguientes:

select profesor0_.Id as Id3_1_, profesor0_.nombre as nombre3_1_, profesor0_.ape1 as ape3_3_1_, profesor0_.ape2 as ape4_3_1_, direccion1_.Id as Id1_0_, direccion1_.calle as calle1_0_, direccion1_.numero as numero1_0_, direccion1_.idMunicipio as idMunici4_1_0_, direccion1_.provincia as provincia1_0_ from Profesor profesor0_ left outer join Direccion direccion1_ on profesor0_.Id=direccion1_.Id where profesor0_.Id=?
select Id from Profesor where Id =? for update

Vemos la segunda SQL que es un SELECT … FOR UPDATE.

Session.replicate(Object objeto,ReplicationMode replicationMode)

El método Session.replicate(Object objeto,ReplicationMode replicationMode) se usa para copiar un objeto de una base de datos a otra usando distintas sesiones contra distintas bases de datos. En este curso no vamos a ver cómo copiar datos entre distintas bases de datos.

LazyInitializationException

Veamos ahora un típico error que se puede dar en Hibernate, la excepción org.hibernate.LazyInitializationException. Esta excepción se produce cuando se intenta cargar un objeto en memoria pero la sesión ya se ha cerrado.

En la anterior sesión hablamos del Lazy Loading ( o carga perezosa). Vimos que puede ser muy útil para no cargar excesivos objetos en memoria.

Supongamos el siguiente código:

1
Session session = sessionFactory.openSession();
 
Profesor profesor = (Profesor) session.get(Profesor.class, 1065);
System.out.println("Leido el profesor");
System.out.println("Calle="+profesor.getDireccion().getMunicipio().getNombre());
 
session.close();
  • En la línea 3 se lee el objeto profesor desea la base de datos
  • La línea 4 muestra el mensaje el objeto profesor ha sido leido
  • En la línea 5 se muestra el nombre del municipio donde vive el profesor.

Aparentemente tras la línea 3 ya no se deberían hacer accesos a la base de datos ya que ya hemos hecho el Session.get(Class clazz,Serializable id) para cargar el profesor.Sin embargo la salida por consola es la siguiente:

Hibernate: select profesor0_.Id as Id3_1_, profesor0_.nombre as nombre3_1_, profesor0_.ape1 as ape3_3_1_, profesor0_.ape2 as ape4_3_1_, direccion1_.Id as Id1_0_, direccion1_.calle as calle1_0_, direccion1_.numero as numero1_0_, direccion1_.idMunicipio as idMunici4_1_0_, direccion1_.provincia as provincia1_0_ from Profesor profesor0_ left outer join Direccion direccion1_ on profesor0_.Id=direccion1_.Id where profesor0_.Id=?
Leido el profesor
Hibernate: select municipio0_.idMunicipio as idMunici1_2_0_, municipio0_.codProvincia as codProvi2_2_0_, municipio0_.codMunicipio as codMunic3_2_0_, municipio0_.NombreMunicipio as NombreMu4_2_0_ from Municipios municipio0_ where municipio0_.idMunicipio=?
Municipio=Llaurí

Tras leer el profesor para acceder a los datos de Municipio se realiza otra SELECT contra la base de datos.Si cambiamos el código de la siguiente manera:

1
Session session = sessionFactory.openSession();
 
Profesor profesor = (Profesor) session.get(Profesor.class, 1065);
System.out.println("Leido el profesor");
 
session.close();
 
System.out.println("Municipio="+profesor.getDireccion().getMunicipio().getNombre());

hemos modificado que el objeto profesor pase del estado Persistido al estado Despegado ya que hemos cerrado la sesión en la línea 6. Si ejecutamos el código se producirá la siguiente excepción en la línea 8:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
Ésto de cerrar la sesión y seguir trabajando nos puede parecer una tontería de código pero en una aplicación web, con varias capas y con multitud de frameworks, podemos acabar con código en el que pase ésto sin que nos demos cuenta.

NonUniqueObjectException

Veamos ahora otro típico error que se puede dar en Hibernate, la excepción org.hibernate.NonUniqueObjectException. Esta excepción se produce cuando se intenta que haya más de un objeto en estado Persistido para la misma fila de la base de datos.

Veamos el siguiente código:

1
Session session = sessionFactory.openSession();
session.beginTransaction();
 
Profesor profesor = new Profesor(1001, "LLUIS", "GOMIS", "MARTINEZ");
Profesor profesor2 = (Profesor) session.get(Profesor.class, 1001);
 
profesor.setNombre("JUAN");
session.update(profesor);
 
session.getTransaction().commit();
session.close();

En la línea 4 se crea el objeto profesor con el Id=1001, pero en la línea 5 se crea otro objeto a partir de la base de datos con el mismo Id=1001. Cuando en la línea 8 intentamos actualizar el objeto se produce la excepción:

Exception in thread "main" org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [ejemplo01.Profesor#1001]

¿Cuál ha sido el problema? Volvamos a explicar lo que ha ocurrido usando los estados.

  • En la línea 4 se crea el objeto profesor con el estado Transitorio, es decir que Hibernate no sabe nada de él.
  • En la línea 5 se crea mediante el método get(Class clazz,Serializable id) el objeto profesor2 con el estado Persistido e Hibernate que sabe de este objeto.

¿Qué ocurre en la línea 8? Que mediante el método update queremos persistir el objeto profesor pero Hibernate sabe que el autentico objeto de la clase Profesor con Id=1001 es el objeto profesor2, así que se lanza la excepción NonUniqueObjectException porque hay más de un objeto con Id=1001 e Hibernate no sabe cómo tratar 2 objetos referidos a la misma fila ya que podrían tener distintos valores y quedar el estado de la fila Id=1001 en un estado erróneo.

Ésto de tener más de un objeto para la misma fila nos puede parecer una tontería de código pero en una aplicación web, con varias capas y con multitud de frameworks, podemos acabar con código en el que pase ésto sin que nos demos cuenta.

Referencias

  • merge: Explicación del método Object session.merge(Object).
1)
Ya que previamente lo hemos guardado y ahora lo volvemos a crear con los mismo valores que hay en la base de datos
unidades/06_objetos_validaciones/01_trabajando_objetos.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1