SessionFactory
Tabla de Contenidos
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 aSessionFactory
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 objetoSessionFactory
. 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 laSessionFactory
se guarda en una variable estática de la clase.closeSessionFactory()
(Líneas 33-36): Cierra el objetoSessionFactory
. 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 seanull
(por si no se llamó nuncabuildSessionFactory()
y que no esté ya cerrado (por si ya se ha llamado acloseSessionFactory()
.
openSessionAndBindToThread()
(Líneas 13-16): Crea la nueva sesión. Pero tiene la peculiaridad de que dicha sesión se almacena suando la líneaThreadLocalSessionContext.bind(session)
. Seguidamente explicaremos ésto.closeSessionAndUnbindFromThread()
(Líneas 26-31): Cierra la sesión que se creó medianteopenSessionAndBindToThread()
. Al igual que antes la sacamos de un lugar extraño mediantesession = ThreadLocalSessionContext.unbind(sessionFactory)
.
SessionFactory getSessionFactory()
(Línea 21-23): Nos retorna un objetoSessionFactory
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
.
- 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).
HibernateUtil.closeSessionFactory()
pero no vamos a complicar mas el código.
It is not intended that implementors be threadsafe. Instead each thread/transaction should obtain its own instance from a SessionFactory