Herramientas de usuario

Herramientas del sitio


unidades:07_arquitectura:01_hibernateutil

HibernateUtil

El uso de hibernate nos obliga a tener siempre disponible una referencia al objeto SessionFactory para que cualquier clase pueda tener acceso al objeto Session y por lo tanto a todas las funcionalidades de Hibernate.

Acceso a SessionFactory

El problema de acceder al objeto SessionFactory 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 SessionFactory:

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 Inyección de Dependencias.

Objeto global

Si SessionFactory 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 Inyección de Dependencias mediante el uso del Framework 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 Inyección de Dependencias nos permitirá que se le asigne a cualquier objeto una referencia a SessionFactory mediante el uso de un framework de Inyección de Dependencias como por ejemplo 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 1) 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”.

El código que genera NetBeans es el siguiente:

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;
    }
}
  • 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 Open Session In View 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:

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();
  • 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:

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();
        }
    }    
}

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 ThreadLocalSessionContext. Esta clase nos permite guardar una sesión en una zona de memoria específica del propio Thread en la que se está ejecutando.

ThreadLocalSessionContext.bind(session);

Guarda la sesión en la memoria privada del Thread donde se está ejecutando el código.

Session session = ThreadLocalSessionContext.unbind(sessionFactory);

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:

configuration.setProperty("hibernate.current_session_context_class", "thread");

Configura la clase ThreadLocalSessionContext 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 Open Session In View.

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 2) 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:

Session session=HibernateUtil.getSessionFactory().getCurrentSession();

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

Session session=HibernateUtil.getSessionFactory().getCurrentSession();

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 SessionFactory.getCurrentSession().

Hasta ahora para obtener un objeto Session usábamos el método SessionFactory.openSession() pero ahora deberemos usar el método SessionFactory.getCurrentSession().

¿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.

El uso de esta clase en una aplicación web se ve en SessionFactory e Hibernate.
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();
    }
}

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).

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.
1)
Sí que hay una clase llamada org.hibernate.cache.ehcache.internal.util.HibernateUtil pero no tiene nada que ver con la clase SessionFactory
2)
En org.hibernate.Session se incluye lo siguiente:
It is not intended that implementors be threadsafe. Instead each thread/transaction should obtain its own instance from a SessionFactory
unidades/07_arquitectura/01_hibernateutil.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1