El patron de diseño "open session in view"

Hola a todos. Quien no se a encontrado con la siguiente excepcion usando Hibernate?:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session

Normalmente sucede cuando se trabaja con Spring o cuando sencillamente queremos consultar los datos de una tabla que esta referenciada en otra (many-to-one), o de una manera mas clara sucede como la explica Ezamudio: El problema es porque lees un objeto de una entidad que tiene relación a muchos con otra entidad, el típico maestro-detalle, pero solamente leíste el maestro, que se queda con un falso arreglo en la relación al detalle, que sirve para que una Session de hibernate pueda sustituirlo por un arreglo ya con los detalles. Pero seguramente tienes un DAO o algo similar que lee el objeto, cierra la sesión, lo devuelve, y a la hora que lo quieres desplegar en tu JSP te sale esa excepción porque se intenta resolver el arreglo que nada más está de placeholder y no se puede porque no hay una sesión abierta.

Ok, esto se puede resolver de muchas manera, una es usando anotaciones EJB en tus beans poniendo lazy=false en la anotación de @Proxy de esta manera te ahorras los archivos .hbm.xml pero puedes incurrir en problemas de performances por que el maestro se trae todos los detalles asi sean miles o cientos y los mantiene en memoria. Otra forma es que tu archivos .hbm especifiquen en el atributo fetch="join" en la etiqueta y que en tus hql donde quieras conocer lo detalles del maestro lo especifiques de esta manera:
from Maestro as maestro left join fetch maestro.detalle, pero nos vamos a concentar en solucionarlo usando el patron "open session in view", el cual me han recomendado, si alguien encuentra algun error ya sea de logica, sintaxis, o de cualquier indole favor de avisarme para corregirlo.

Vamos a verlo usando un ejemplo sencillo con dos tablas (Profesor y Alumno), los beans serian los siguientes:

Bean Alumno

package beans;

public class Alumno  implements java.io.Serializable
{
     private Integer idAlumno;
     private Profesor profesor;
     private String nombre;

    public Alumno() {
    }

    public Alumno(Profesor profesor, String nombre) {
       this.profesor = profesor;
       this.nombre = nombre;
    }
   
    public Integer getIdAlumno() {
        return this.idAlumno;
    }
   
    public void setIdAlumno(Integer idAlumno) {
        this.idAlumno = idAlumno;
    }
    public Profesor getProfesor() {
        return this.profesor;
    }
   
    public void setProfesor(Profesor profesor) {
        this.profesor = profesor;
    }
    public String getNombre() {
        return this.nombre;
    }
   
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
}

Bean Profesor:

import java.util.HashSet;
import java.util.Set;

public class Profesor  implements java.io.Serializable {

     private Integer idProfesor;
     private String nombre;
     private Set<Alumno> alumnos = new HashSet<Alumno>(0);

    public Profesor() {
    }

    public Profesor(String nombre, Set<Alumno> alumnos) {
       this.nombre = nombre;
       this.alumnos = alumnos;
    }
   
    public Integer getIdProfesor() {
        return this.idProfesor;
    }
   
    public void setIdProfesor(Integer idProfesor) {
        this.idProfesor = idProfesor;
    }
    public String getNombre() {
        return this.nombre;
    }
   
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    public Set<Alumno> getAlumnos() {
        return this.alumnos;
    }
   
    public void setAlumnos(Set<Alumno> alumnos) {
        this.alumnos = alumnos;
    }
}

Archivo de configuracion de hibernate "hibernate.cfg.xml"

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/bdescuela</property>
    <property name="hibernate.connection.username">user</property>
    <property name="hibernate.connection.password">passwordUser</property>
    <property name="hibernate.show_sql">true</property>
    <property name="hibernate.current_session_context_class">thread</property>
    <mapping resource="beans/Profesor.hbm.xml"/>
    <mapping resource="beans/Alumno.hbm.xml"/>
  </session-factory>
</hibernate-configuration>

Sevlet que implementa la interfaz Filter:

package util;
import javax.servlet.*;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.SessionFactory;

/**
 *
 * @author JavAdicto
 */

public class SessionManager implements Filter
{
    private static final SessionFactory sessionFactory;

    static
    {
        try
        {
            //Aqui creamos una session de la fabrica de session usando
            //el archivo de configuracion hibernate.cfg.xml
            sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        }
        catch (Throwable ex)
        {
           
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

   //Aqui en donde deberia crear una session de la fabrica de sessiones por que es el metodo init y este metodo se hizo para
   //inicializar valores o solicitar recursos, pero eso ya lo hice arriba.
    public void init(FilterConfig filterConfig) throws ServletException
    {        
    }

    //Este metodo se debe declarar por que viene especificado en la interfaz y es donde se deberia asignar una session
    //a un thread local, pero eso ya viene especificado en el archivo de configuracion hibernate.cfg.xml
    //<property name="hibernate.current_session_context_class">thread</property>
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    {
    }

    //Para obtener una session
    public static Session getSession()
    {
        return sessionFactory.getCurrentSession();
    }

    //El metodo destroy se manda a llamar automaticamente al terminar el request, de esta manera
   //aseguramos no dejar la session abierta
    public void destroy()
    {
        try
        {
            sessionFactory.close();
        }
        catch (HibernateException ex)
        {
            throw new RuntimeException(ex);
        }
    }
}

Archivo de configuracion "web.xml"

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee <a href="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
" title="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
">http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</a>    
    <filter>
        <filter-name>sessionmanager</filter-name>
        <filter-class>util.SessionManager</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sessionmanager</filter-name>
        <servlet-name>SessionManager</servlet-name>
    </filter-mapping>
   
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
   
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
       
</web-app>

Clase con el metodo que realiza la consulta:

package services;

import beans.Alumno;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import util.SessionManager;

/**
 *
 * @author JavAdicto
 */

public class metodos
{
    public Alumno getAlumnoPorId(String id)
    {
        Alumno alumno = null;
        try
        {
            Session session = SessionManager.getSession();
            org.hibernate.Transaction tx = session.beginTransaction();

            Query query = session.createQuery("from Alumno as a where a.idAlumno="+id);
            alumno = (Alumno)query.uniqueResult();

        } catch (HibernateException e)
        {
            e.printStackTrace();
        }

        return alumno;
    }
}

De esta forma podemos consultar de la siguiente manera sin problemas:

metodos m = new metodos();

Alumno alumno = m.getAlumno("1");
System.out.println("Alumno: "+alumno.getNombre());
Profesor profe = alumno.getProfesor();
System.out.println("Profesor: "+profe.getNombre());

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ecamacho

gran post

Muy bueno javadicto, me gustó bastante. Sería bueno que incluyeras un war con el fuente y la aplicación corriendo para que lo tomen de ejemplo.

Imagen de javadicto

Hola ecamacho gracias por tu

Hola ecamacho gracias por tu comentario. Claro vere donde puedo subir el proyecto completo y lo agrego al post, esta hecho en NetBeans asi que no hay ningun problemas en importarlo y en la carpeta "dist" hayan el .war.

Imagen de pinha_xtreme

Duda transaction

Hola javAdicto, espero puedas profundizar un poco más en el tema de este patrón, ya que me surgieron algunas dudas, la primera de ellas: en que momento se invoca el método destroy() del administrador de sesiones, será cuando se pierda la referencia del bean que invoca el servicio de consulta???, la otra, veo que estas abriendo una transaccion, que pasa con la transaccion si nunca se le hace commit o rollback y si nunca hay operaciones de actualizacion o alta de datos, cual es el fin de abrir la transaccion?

Imagen de javadicto

Que paso pinha_xtreme

El metodo init() y destroy() se invocan automaticaente en un servlet, el primero para inicializar atributos o para solicitar recursos y el segundo para liberar estos recursos o lo que se haya solicitado, el metodo destroy() el cual cierra la session se manda a llamar cuando se termina la peticion(request). El otro punto se me paso realizar mas operaciones de ejemplo como el save(), load(), etc. Si es necesario el commit() para asefurar la transaccion o el rollback() en el catch si este genera una exepcion y dejar todo como estaba, despues expondre mas metodo de ejemplo. Gracias por comentar.

Imagen de javadicto

Sale, te expongo los metodos

interface GenericDAO:

package model.dao;

import java.util.Collection;
import java.util.List;

/**
 *
 * @author JavAdicto
 */

public interface GenericDAO
{
    public void guardar(Object object);
    public void actualizar(Object object );
    public void eliminar(Object instanciaPersistente);
    public Object buscarPorId(Class<?> clase,long id);
    public List buscarTodo(Class clase);
    public void guardarTodo(Collection collection);
}

clase metodos:

package model.dao;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 *
 * @author JavAdicto
 */

public class metodos implements GenericDAO
{
    public void guardar(Object object)
    {        
        Transaction tx = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            session.saveOrUpdate(object);
            tx.commit();
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }
    }

    public void actualizar(Object object )
    {        
        Transaction tx = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            session.update(object);
            tx.commit();
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }
    }

    public void eliminar(Object object)
    {        
        Transaction tx = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            session.delete(object);
            tx.commit();
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }
    }

    public Object buscarPorId(Class<?> clase,long id)
    {        
        Transaction tx = null;
        Object object = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            object = session.get(clase, id);
            tx.commit();
            session.flush();
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }

        return object;
    }

    public List buscarTodo(Class clase)
    {        
        Transaction tx = null;
        List list = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            Query query = session.createQuery("from "+clase.getSimpleName());
            list = query.list();
            tx.commit();
            session.flush();
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }

        return list;
    }

    public void guardarTodo(Collection collection)
    {        
        Transaction tx = null;
        try
        {
            Session session = SessionManager.getSession();
            tx = session.beginTransaction();
            Iterator it=collection.iterator();

            while(it.hasNext())
            {
                session.save(it.next());
            }
        }
        catch(Exception e)
        {
            if(tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch(Exception e2)
                {
                    e2.printStackTrace();
                }
            }
            e.printStackTrace();
        }
    }
}

Imagen de jiturbide

Observaciones a la implementacion de open session in view

Hola

Es necesario hacer unas observaciones.
* Un filtro no es un servlet, es un filtro de servlets que intercepta peticiones, para que realmente se active debes colocar en el web xml que tipo de peticiones (jsps, servlets o un patron *) deben interceptarse por el filltro y hacer todo el trabajo dentro del metodo doFilter().
* Los metodos init y destroy de un filtro y un servlet solo se llaman en el arranque del server y en la finalizacion correcta de la aplicacion web, no por cada peticion.
* Lo que realmente esta pasando es que estas abriendo la sesion cuando se instancia el filtro en el arranque del server y esta sesion solo se va a cerrar cuando la aplicacion web finalice.
* Un filtro es una solucion muy costosa si filtras todas las peticiones, por ello se debe colocar en el web.xml solo las peticiones que deben ser filtradas y colocar dentro del metodo doFilter validaciones para que actue solo cuando sea necesario
* No conozco mucho de la combinacion Spring-Hibernate pero creo que debe haber alguna forma de hacer esto en la capa de integracion y no en la de presentacion.
* Suponiendo que el codigo es correcto, que pasaria con aquellas peticiones que no tengan que ver con alumno ni con profesor, de todas maneras abririan la sesion?
* Pecata minuta: es mejor Metodos que metodos

Envio un link de un libro para revisar el funcionamiento de los filtros
http://earth.flazx.net/preview/c8128c/flazx_scwcd-exam-study-kit-second-...
En este link toda la logica de filtrado esta dentro del metodo doFilter. https://www.hibernate.org/43.html

Saludos

Imagen de javadicto

Interesante

Es interesante lo que comentas. Por eso muy explicitamente puse: "si alguien encuentra algun error ya sea de logica, sintaxis, o de cualquier indole favor de avisarme para corregirlo". Gracias jIturbide por comentar, voy a checar lo que dices y cuando tenga el codigo corregido lo agregare en el post.

Imagen de jali

Muy bueno

Buen post! cuando me paso me estuve dando de topes jejeje.
Saludos

El cache de Hibernate

Otro punto a considerar en la implementacion de este Patron de Diseño es el echo de que Hibernate maneja dos niveles de Cache, por lo cual puede ser peligroso que nunca se haga un Flush de la sesion, ya que estará almacenando en el cache de segundo nivel todos los movimientos hasta que llegue un momento en que la aplicación se chute toda la memoria y se caiga, esto se puede corregir realizando un flush manualmente después de cada unidad de trabajo. Lo mejor seria integrar Spring con Hibernate para ahorrarte esos posibles dolores de cabeza.

Saludos

Imagen de javadicto

Que tal josebetomex

Buen punto el que expones, por que de eso se trata de evitar posibles problemas de rendimiento y sobre todo caidas del sistemas, pero sabes que seria excelente que acompañaras tu comentario con codigo de ejemplo, algo sencillo y practico, por que la mayoria de la gente que expone sus problemas o soluciones no lo hacen y eso seria como leer un libro de programacion que solo contiene teoria y definiciones, claro que las deficiones sirven pero de lo que realmente aprendes es de codigos de ejemplo que puedas compilar y ejecutar.

Cache de Hibernate

Que tal Javadicto

Un ejemplo sencillo podria ser el siguiente:

         ....
       Transaction tx = session.beginTransaction();
       for ( int i=0; i<100000; i++ ) {
               Cliente cliente = new Cliente();
               cliente.setNombre(...):
                ... //Todos los Geters
               session.save(cliente);
        }
        tx.commit();
        session.close();
   

En ejemplo anterior se obtiene la sesion, se realizan determinado numero de operaciones, en este ejemplo 100000 inserciones y despues se cierra la sesion (Esto concluiria con un Flush). Hibernate en cada inserción estará almacenando en su cache de Segundo Nivel cada uno de los Pojos que estas creando, consumiéndose la memoria, lo cual provocara que en algún punto se la termine y truene la aplicación y nunca llegue a cerrar a sesion. Cabe aclarar que no es el Cache de primer Nivel que tipicamente se configura con EHCACHE.

Lo correcto cuando se usa Hibernate es cerrar la sesion despues de que has realizado cada bloque de trabajo, el ejemplo anterior se podria prevenir que se termine al memoria haciendo lo siguiente:

    .....
    for ( int i=0; i<100000; i++ ) {
        Cliente cliente = new Cliente();
        cliente.setNombre(...):
        ... //Todos los seters
        session.save(cliente);
        if ( i % 10 == 0 ) {  
             session.flush();
             session.clear();
         }
    }
     session.close();

Otra forma de prevenir un truene es deshabilitando el cache de segundo nivel.

hibernate.cache.use_second_level_cache=false

Espero que con este ejemplo se aclare un poco la idea que quiero transmitir.

Saludos

Session Per Request (open session in view)

El metodo yo lo uso y es bueno, lo unico malo de usar un filtro es que wrapea TODOS los request, es decir, cuando el cliente pide una imagen, un css, un js... tambien se ejecuta. En mi caso uso tapestry5 y solamente uso el patron cuando se va a render() la pagina o un componente, haciendo que se llame solo cuando es necesario. Es un detalle de performance solamente. Saludos!

Imagen de ezamudio

T5

Si usas Tapestry 5, utiliza Assets para imágenes, CSS, javascripts, etc y configura el modo producción para que todos esos assets estáticos se copien a un directorio en el htdocs de tu web server, de modo que Tapestry (y el contenedor) ya no sirvan esos recursos sino que lo haga directamente el httpd.

Imagen de ale_imp

No entiendo

Hola buena tarde espero puedan ayudarme acabo de hacer un ejemplo, con la implementación que nos explico en este post javadicto. Mi inquietud es esta, tengo una aplicación en la cual estoy utilizando struts 2 y hibernate y me gustaria implementarle para fines de aprendizaje spring. Como lo mencione anteriormente mi aplicacion funciona prefectamente usanado estas tenologias antes mencionadas y con la Impleentacion de este post.. Por donde puedo empezar para q le agrege este patron open session in view pero usando spring. Aclaro es para fines de aprendizaje.

Imagen de ale_imp

Bueno acabo de encontrar un link

donde mencionan que se le agregue este filtro

<filter>
        <filter-name>hibernateSessionInViewFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
   </filter>

    <filter-mapping>
        <filter-name>hibernateSessionInViewFilter</filter-name>
        <url-pattern>/a/*</url-pattern>
    </filter-mapping>

Pero en la parte del applicationContex.xml como se definiria. Alguna idea

Imagen de benek

Antipattern

Tengan cuidado con Open Session In View, ha sido considerado un anti-patrón.

Les recomiendo esta lectura: http://java.dzone.com/articles/opensessioninview-antipattern

Saludos.