Herramientas de usuario

Herramientas del sitio


unidades:06_objetos_validaciones:01_trabajando_objetos
no way to compare when less than two revisions

Diferencias

Muestra las diferencias entre dos versiones de la página.


unidades:06_objetos_validaciones:01_trabajando_objetos [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== 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:
 +{{ :unidades:estados.jpg |}}
 +
 +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 (( Ya que previamente lo hemos guardado y ahora lo volvemos a crear con los mismo valores que hay en la base de datos )) 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 <javadoc h41>org.hibernate.Session#close|Session.close()</javadoc> o llamamos al método <javadoc h41>org.hibernate.Session#evict(java.lang.Object)|Session.evict(Object objeto)</javadoc> 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 [[unidades:03_relaciones:06_cascade]] ya se habló de varios métodos que tiene la clase <javadoc h41>org.hibernate.Session</javadoc>.Pasemos ahora a explicarlos desde el punto de vista de los estados internos de Hibernate.
 +
 +==== Session.evict(Object objeto) ====
 +El método <javadoc h41>org.hibernate.Session#evict(java.lang.Object)|Session.evict(Object objeto)</javadoc> hace que un objeto en estado Persistido pase al estado Despegado y lo borre de su cache.
 +
 +<code java 1>
 +Session session = sessionFactory.openSession();
 +Profesor profesor =(Profesor)session.get(Profesor.class,1001);
 +session.evict(profesor);
 +session.close();
 +</code>
 +==== Session.merge(Object objeto) ====
 +El método <javadoc h41>org.hibernate.Session#merge(java.lang.Object)|Session.merge(Object objeto)</javadoc>  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.
 +
 +<code java 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();
 +</code>
 +
 +  * 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 <javadoc h41>org.hibernate.Session#refresh(java.lang.Object)|Session.refresh(Object objeto)</javadoc> es similar a <javadoc h41>org.hibernate.Session#merge(java.lang.Object)|Session.merge(Object objeto)</javadoc> 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.
 +
 +<code java 1>
 +Session session = sessionFactory.openSession();
 +Profesor profesor = new Profesor(1001,"LLUIS", "GOMIS", "MARTINEZ");
 +session.refresh(profesor);
 +session.close();
 +</code>
 +
 +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 <javadoc h41>org.hibernate.Session$LockRequest#lock(java.lang.Object)| LockRequest.lock(Object objeto)</javadoc> no modifica el estado interno del objeto pero sí que permite hacer un ''SELECT ... FOR UPDATE'' contra la base de datos.
 +
 +Veamos un ejemplo.
 +<code java 1>
 +Session session = sessionFactory.openSession();
 +Profesor profesor =(Profesor)session.get(Profesor.class,1001);
 +session.buildLockRequest(LockOptions.UPGRADE).lock(profesor);
 +session.close();
 +</code>
 +
 +  * 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 <javadoc h41>org.hibernate.Session#replicate(java.lang.Object, org.hibernate.ReplicationMode)|Session.replicate(Object objeto,ReplicationMode replicationMode)</javadoc> 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 <javadoc h41>org.hibernate.LazyInitializationException</javadoc>. 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 [[unidades:05_hibernate_query_language:05_optimizacion#solucion_con_lazy_loading|Lazy Loading]] ( o carga perezosa). Vimos que puede ser muy útil para no cargar excesivos objetos en memoria. 
 +
 +Supongamos el siguiente código:
 +<code java 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();
 +</code>
 +
 +  * 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 <javadoc h41>org.hibernate.Session#get(java.lang.Class, java.io.Serializable)|Session.get(Class clazz,Serializable id)</javadoc> 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:
 +<code java 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());
 +</code>
 +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
 +  
 +<note tip>
 +É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.
 +</note>
 +
 +===== NonUniqueObjectException  =====
 +Veamos ahora otro típico error que se puede dar en Hibernate, la excepción <javadoc h41>org.hibernate.NonUniqueObjectException</javadoc>. 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:
 +<code java 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();
 +</code>
 +
 +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 <javadoc h41>org.hibernate.Session#get(java.lang.Class, java.io.Serializable)|get(Class clazz,Serializable id)</javadoc> el objeto ''profesor2'' con el estado Persistido e Hibernate **sí** 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 <javadoc h41>org.hibernate.NonUniqueObjectException|NonUniqueObjectException</javadoc> 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.
 +
 +<note tip>
 +É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.
 +</note>
 +
 +===== Referencias =====
 +    * [[http://www.hibernate-training-guide.com/merge.html|merge]]: Explicación del método ''Object session.merge(Object)''.
unidades/06_objetos_validaciones/01_trabajando_objetos.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1