Herramientas de usuario

Herramientas del sitio


unidades:07_arquitectura:01_hibernateutil

Diferencias

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


unidades:07_arquitectura:01_hibernateutil [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== HibernateUtil ======
 +El uso de hibernate nos obliga a tener siempre disponible una referencia al objeto <javadoc h41>org.hibernate.SessionFactory|SessionFactory</javadoc> para que cualquier clase pueda tener acceso al objeto <javadoc h41>org.hibernate.Session|Session</javadoc> y por lo tanto a todas las funcionalidades de Hibernate.
  
 +===== Acceso a  SessionFactory =====
 +El problema de acceder al objeto <javadoc h41>org.hibernate.SessionFactory|SessionFactory</javadoc> es que //cualquier clase// podría necesitarlo por lo que deberemos hacérselo llegar de alguna forma.
 +
 +Hay tres técnicas  para que cualquier clase tenga acceso a <javadoc h41>org.hibernate.SessionFactory|SessionFactory</javadoc>:
 +  * [[#Paso del objeto entre clases]]. 
 +  * [[#Objeto global]].
 +  * [[#Inyección de Dependencias]].
 +
 +==== Paso del objeto entre clases ====
 +Esta técnica consiste en el clásico método de ir pasando el objeto SessionFactory por todos los objetos hasta llegar al que finalmente lo necesita. Es decir, que //ensuciaremos// muchas clases hasta llegar a la que finalmente usa SessionFactory para de esa forma evitarnos un objeto global. 
 +
 +Esta técnica ya está en desuso y mejor usar objetos globales o [[patrones:di]].
 +
 +==== Objeto global ====
 +Si <javadoc h41>org.hibernate.SessionFactory|SessionFactory</javadoc> está en una variable global , cualquier objeto que lo necesite podrá fácilmente acceder a él. En Java no existen las variable globales pero es sencillo //simular// una variable global  mediante el uso de método estáticos. Esta técnica suele corresponder a patrones de diseño del tipo factoría.
 +
 +Ésta es la técnica que vamos a explicar ahora ,pero posteriormente la cambiaremos por la  [[patrones:di]] mediante el uso del Framework [[http://www.springsource.org/|Spring]] . Incluimos primero esta técnica para que luego sea más sencillo el uso de la Inyección de Dependencias.
 +
 +==== Inyección de Dependencias ====
 +La [[patrones:di]] nos permitirá que se le asigne a cualquier objeto una referencia a <javadoc h41>org.hibernate.SessionFactory|SessionFactory</javadoc> mediante el uso de un framework de Inyección de Dependencias como por ejemplo [[http://www.springsource.org/|Spring]]. De esta forma nos evitamos la variable global o el paso del objeto entre clases.
 +
 +En los siguientes temas seguiremos hablando sobre Inyeccion de Dependencias y Spring.
 +
 +===== HibernateUtil en NetBeans =====
 +En cualquier libro o tutorial que hable sobre Hibernate acabará hablándose de la clase ''HibernateUtil''. Esta clase que debemos crearnos nosotros y que no está incluida en Hibernate (( Sí que hay una clase llamada <javadoc h41>org.hibernate.cache.ehcache.internal.util.HibernateUtil</javadoc> pero no tiene nada que ver con la clase ''SessionFactory'' )) contiene código estático que inicializa Hibernate y crea el objeto ''SessionFactory''. Se incluye además un método estático que da acceso al objeto ''SessionFactory'' que se ha creado.
 +
 +Esta clase es tán //famosa// que hasta el propio NetBeans tiene una opción para crearla, si vamos a la opción de menú "File --> New File ..." y seleccionamos en el árbol "Hibernate" veremos a la derecha la opción "HibernateUtil.java".
 +
 +{{ :unidades:hibernateutil.png |}}
 +
 +El código que genera NetBeans es el siguiente:
 +
 +<code java 1 | HibernateUtil creado por NetBeans>
 +public class HibernateUtil {
 +
 +    private static final SessionFactory sessionFactory;
 +    
 +    static {
 +        try {
 +            // Create the SessionFactory from standard (hibernate.cfg.xml) 
 +            // config file.
 +            sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
 +        } catch (Throwable ex) {
 +            // Log the exception. 
 +            System.err.println("Initial SessionFactory creation failed." + ex);
 +            throw new ExceptionInInitializerError(ex);
 +        }
 +    }
 +    
 +    public static SessionFactory getSessionFactory() {
 +        return sessionFactory;
 +    }
 +}
 +</code>
 +
 +  * En las líneas 5-15 vemos un código estático que crea el objeto ''SessionFactory'' según la forma de hibernate 3 por lo que dicho código no nos sirve.
 +  * En las línea 17-19 está el método estático ''SessionFactory getSessionFactory()'' que nos permitirá acceder a ''SessionFactory'' desde cualquier clase como si se tratara de un objeto global.
 +
 +
 +¿Por qué si es tan útil esta clase no está incluida en el propio Hibernate? Pues porque cada uno se la puede implementar según sus propias necesidades. Hemos visto la clase ''HibernateUtil'' generada por NetBeans, sin embargo , desde mi punto de vista esta clase tiene los siguientes problemas:
 +  * Está anticuada al usar el código de Hibernate 3
 +  * No permite pasar ningún parámetro de inicialización como por ejemplo indicar otra posible ubicación de ''hibernate.cfg.xml''.
 +  * No incluye código para cerrar el objeto ''SessionFactory'' aunque sí para crearlo lo que hace que sea poco ortogonal.
 +  * No ayuda a implementar el patrón [[patrones:osiv]] en una aplicación web.
 +===== Mi versión de HibernateUtil =====
 +Antes de mostrar la clase ''HibernateUtil'' que he creado veamos el código Java que estamos usando de Hibernate en todos nuestros ejemplos:
 +
 +<code java 1>
 +SessionFactory sessionFactory;
 +Session session;
 +
 +
 +Configuration configuration = new Configuration();
 +configuration.configure();
 +ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
 +sessionFactory = configuration.buildSessionFactory(serviceRegistry);
 +
 +session = sessionFactory.openSession();
 +
 +//Trabajar con la sesión
 +
 +session.close();
 +
 +sessionFactory.close();
 +</code>
 +
 +  * La linea 1 es la variable donde guardaremos el objeto ''SessionFactory''
 +  * La linea 2 es la variable donde guardaremos el objeto ''Session''
 +  * Las líneas 5 a 8 crean el objeto ''SessionFactory ''.
 +  * La línea 10 crea el objeto ''Session''
 +  * La línea 14 cierra el objeto ''Session''
 +  * La línea 16 cierra el objeto ''SessionFactory ''
 +
 +Ahora veamos el código de la clase ''HibernateUtil'' que he creado y observemos la similitudes con el código que estamos usando durante todo el curso:
 +<code java 1 | HibernateUtil.java >
 +public class HibernateUtil {
 +
 +    private static SessionFactory sessionFactory;
 +
 +    public static synchronized void buildSessionFactory() {
 +        if (sessionFactory == null) {
 +            Configuration configuration = new Configuration();
 +            configuration.configure();
 +            configuration.setProperty("hibernate.current_session_context_class", "thread");
 +            ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
 +            sessionFactory = configuration.buildSessionFactory(serviceRegistry);
 +        }
 +    }
 +
 +    public static void openSessionAndBindToThread() {
 +        Session session = sessionFactory.openSession();
 +        ThreadLocalSessionContext.bind(session);
 +    }
 +
 +
 +    public static SessionFactory getSessionFactory() {
 +        if (sessionFactory==null)  {
 +            buildSessionFactory();
 +        }
 +        return sessionFactory;
 +    }
 +
 +    public static void closeSessionAndUnbindFromThread() {
 +        Session session = ThreadLocalSessionContext.unbind(sessionFactory);
 +        if (session!=null) {
 +            session.close();
 +        }
 +    }
 +    
 +    public static void closeSessionFactory() {
 +        if ((sessionFactory!=null) && (sessionFactory.isClosed()==false)) {
 +            sessionFactory.close();
 +        }
 +    }    
 +}
 +</code>
 +
 +Pasemos ahora a explicar cada una de las funciones de esta clase:
 +
 +  * ''buildSessionFactory()'' (Líneas 5-11):Crea el objeto ''SessionFactory''. El código es prácticamente igual al que ya conocíamos excepto por dos cosas. La línea 8 que explicaremos posteriormente y que la ''SessionFactory'' se guarda en una variable estática de la clase.
 +  * ''closeSessionFactory()'' (Líneas 33-36): Cierra el objeto ''SessionFactory''. Como ya explicamos, si la propia HibernateUtil tiene un método encargado de crear el objeto, también debería tener otro para //finalizarlo// haciendo que todo sea más ortogonal.La única peculiaridad es que comprobamos que el objeto no sea ''null'' (por si no se llamó nunca ''buildSessionFactory()'' y que no esté ya cerrado (por si ya se ha llamado a ''closeSessionFactory()''.
 +
 +  * ''openSessionAndBindToThread()'' (Líneas 13-16): Crea la nueva sesión. Pero tiene la peculiaridad de que dicha sesión se almacena suando la línea ''ThreadLocalSessionContext.bind(session)''. Seguidamente explicaremos ésto.
 +  * ''closeSessionAndUnbindFromThread()'' (Líneas 26-31): Cierra la sesión que se creó mediante ''openSessionAndBindToThread()''. Al igual que antes la sacamos de un lugar //extraño// mediante ''session = ThreadLocalSessionContext.unbind(sessionFactory)''.
 +
 +  * ''SessionFactory getSessionFactory()'' (Línea 21-23): Nos retorna un objeto ''SessionFactory'' que hemos guardado en la variable estática.
 +==== ThreadLocalSessionContext ====
 +La único que desconocemos de  ''HibernateUtil'' es la clase <javadoc h41>org.hibernate.context.internal.ThreadLocalSessionContext|ThreadLocalSessionContext</javadoc>. Esta clase nos permite guardar una sesión en una zona de memoria específica del propio Thread en la que se está ejecutando.
 +
 +<code java>
 +ThreadLocalSessionContext.bind(session);
 +</code>
 +Guarda la sesión en la memoria privada del Thread donde se está ejecutando el código.
 +
 +<code java>
 +Session session = ThreadLocalSessionContext.unbind(sessionFactory);
 +</code>
 +Recupera la sesión y la borra de la memoria privada del Thread donde se está ejecutando el código.
 +
 +
 +Por último la línea:
 +<code java>
 +configuration.setProperty("hibernate.current_session_context_class", "thread");
 +</code>
 +Configura la clase <javadoc h41>org.hibernate.context.internal.ThreadLocalSessionContext|ThreadLocalSessionContext</javadoc> para que funcione de la manera que acabamos de describir.
 +
 +
 +Ahora bien, ¿para que queremos guardar un objeto ''Session'' en una zona de mamoria privada al Thread en el que se está ejecutando el código?
 +
 +Todo ésto se está haciendo para que ''HibernateUtil'' pueda ser usado en una aplicación web sin cambiar la propia ''HibernateUtil'' y además que ayude a implementar el patrón [[patrones:osiv]].
 +
 +En una aplicación web habrá a la vez muchos objetos ''Session'', ya que cada petición web va a necesitar la suya propia. Esto es así ya que según la documentación de Hibernate (( En <javadoc h41>org.hibernate.Session</javadoc> se incluye lo siguiente: \\ //It is not intended that implementors be threadsafe. Instead each thread/transaction should obtain its own instance from a SessionFactory// )) el objeto ''Session'' no puede compartirse entre varias Threads.
 +
 +Así que lo que queremos crear es un objeto ''Session'' al inicio de una petición web y guardarlo en un lugar que sea privado para ese Thread , ya que una petición web y un thread están unidos, por lo que a efectos prácticos son lo mismo. De esa forma cada Thread podrá acceder a su propio objeto ''Session'' y nunca los compartiremos entre Thread , cosa que como ya hemos visto Hibernate prohíbe explicitamente.
 +
 +Lo que nos falta por explicar es como recuperamos la sesión guardada en la zona de memoria provada del Thread.Mediante el codigo siguiente:
 +<code java>
 +Session session=HibernateUtil.getSessionFactory().getCurrentSession();
 +</code>
 +El quid de la cuestión es que esta línea obtendrá una sesión distinta en cada petición/thread web sin que se comparta nunca la sesión entre peticiones/threads
 +
 +
 +Recopilando , el método ''openSessionAndBindToThread()'' lo que hace es crear una nueva sesión y guardarla en un lugar privado del thread actual para que posteriormente mediante la línea 
 +<code java>
 +Session session=HibernateUtil.getSessionFactory().getCurrentSession();
 +</code> 
 +Obtengamos siempre la sesión asociada a nuestra petición/thread.
 +
 +Es decir que hemos creado un ''HibernateUtil'' que funciona correctamente en una aplicación de escritorio pero que también la podremos usar sin ningún tipo de problemas en una aplicación web.
 +
 +==== getCurrentSession() ====
 +Volvamos otra vez sobre el método <javadoc h41>org.hibernate.SessionFactory#getCurrentSession()|SessionFactory.getCurrentSession()</javadoc>.
 +
 +Hasta ahora para obtener un objeto ''Session'' usábamos el método <javadoc h41>org.hibernate.SessionFactory#openSession()|SessionFactory.openSession()</javadoc> pero ahora deberemos usar el método <javadoc h41>org.hibernate.SessionFactory#getCurrentSession()|SessionFactory.getCurrentSession()</javadoc>.
 +
 +¿Qué diferencia hay entre ellos?
 +
 +El método ''openSession()'' siempre crea una nueva sesión , mientras que ''getCurrentSession()'' nos retorna una sesión que ya existe en el thread actual.
 +
 +
 +
 +===== Main =====
 +Veamos ahora cómo inicialmente queda la clase ''Main'' al usar ''HibernateUtil''.
 +
 +<note tip>
 +El uso de esta clase en una aplicación web se ve en [[unidades:08_spring:03_spring_mvc#sessionfactory_e_hibernate|SessionFactory e Hibernate]].
 +</note>
 +
 +<code java 1>
 +public class Main {
 +
 +    public static void main(String[] args) {
 +        HibernateUtil.buildSessionFactory();
 +
 +        try {
 +            HibernateUtil.openSessionAndBindToThread();
 +
 +            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
 +            Profesor profesor = (Profesor) session.get(Profesor.class, 1001);
 +            System.out.println(profesor.toString());
 +        } finally {
 +            HibernateUtil.closeSessionAndUnbindFromThread();
 +        }
 +
 +        HibernateUtil.closeSessionFactory();
 +    }
 +}
 +</code>
 +
 +Como podemos ver, ha quedado todo muy sencillo ya que seguimos los 5 pasos que necesitamos en Hibernate:
 +  * Línea 4: Inicializamos Hibernate.
 +  * Línea 7: Creamos la sesión y la enlazamos al thread actual  
 +  * Línea 9: Obtenemos la sesión.
 +  
 +  * Línea 13: Cerramos la sesión.
 +  * Línea 16: Cerramos Hibernate.
 +
 +La única peculiaridad son las líneas 6 y 12 con el ''try-finally''. Esto se hace ya que si se produjera una excepción no se cerraría la sesión , así que necesitamos un ''try-finally'' que garantiza que siempre se cierre la sesión (línea 13).
 +
 +<note tip>
 +También nos faltaría garantizar que se cierre Hibernate mediante la llamada a ''HibernateUtil.closeSessionFactory()'' pero no vamos a complicar mas el código.
 +</note>