Monday, June 13, 2011

Hibernate - Part 7 - Closing Hibernate Sessions using Open Session in View pattern in Struts2

My initial approach for creating and storing the Hibernate SessionFactory worked fine until I added c3p0 connection pooling as described in my last blog post.

It wasn't long after enabling c3p0 that my application began regularly hanging.  The app would hang every time it reached the maximum connection pool size (c3p0.max_size), which was currently configured for 20 connections.  Why were my connections not getting reused?  As I examined my code for creating Hibernate SessionFactory and Hibernate Sessions, I realized that I was creating Sessions but never closing them.  Therefore, the app was keeping connections open and creating a new connection for each request until it maxed out.

In an effort to solve this problem, I added a session.close() in a finally block in the Struts 2 Action class method.  This only succeeded in throwing "LazyInitializationException" errors in my app as follows:
ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: org.robbins.flashcards.model.Tag.flashcards, no session or session was closed
The LazyInitializationException gets thrown when Hibernte tries to access the database but your Session is already closed.  In my case, this was occurring in my View (JSP).  This is an expected problem and is explained in detail in this article in the Hibernate community.

The article proposes the Open Session in View (OSIV) pattern as a commonly used solution to this issue in two-tier environments.  The Open Session in View pattern keeps the Hibernate Session open until the View has completed rendering.  This pattern is not without its drawbacks.  For more information on the advantages and disadvantages of this approach I recommend reading this discussion on StackOverflow and this blog post by Chris Upton.

At this point, I decided to replace my existing solution for storing the Hibernate SessionFactory with a OSIV approach using a custom Struts 2 interceptor:

public class HibernateSessionInterceptor extends AbstractInterceptor    {

    /**
     * Holds the current hibernate session, if one has been created.
     */
    protected static ThreadLocal <Session> hibernateHolder = new ThreadLocal <Session> (); 
  
    protected static SessionFactory factory;
    
    static Logger logger = Logger.getLogger(HibernateSessionInterceptor.class);
  
    public String intercept(final ActionInvocation invocation) throws Exception {
        logger.debug("Entering intercept()");

        if (factory == null) {
            logger.debug("Hibernate SessionFactory is null.  Creating new SessionFactory.");
            factory = new Configuration().configure().buildSessionFactory();
        }
        
        // if a Hibernate Session doesn't already exist in the ThreadLocal
        if (hibernateHolder.get() == null) {
            logger.debug("Hibernate Session not found in ThreadLocal");

            // get a Hibernate Session and place it in ThreadLocal
            getSession();
        }
        
        try {  
            // invoke the Action
            return invocation.invoke();
        }
        finally {
            logger.debug("Entering finally{} block of intercept()");
            
            Session sess = (Session)hibernateHolder.get( );

            if (sess != null) {
                logger.debug("Hibernate Session found in ThreadLocal.  Setting Session to null in ThreadLocal.");
                
                hibernateHolder.set(null);
              
                try {
                    logger.debug("Closing Hibernate Session");
                    sess.close( );
                }
                catch (HibernateException ex) { 
                    logger.error("Exception in doFilter():", ex);
                    throw new ServletException(ex);
                }
            }
            else {
                logger.debug("Could not find Hibernate session");
            }
        }
    }
  
    public static Session getSession( ) throws HibernateException {
        logger.debug("Entering getSession()");
        
        Session sess = (Session)hibernateHolder.get();
      
        if (sess == null) {
            logger.debug("Getting a Hibernate Session from the SessionFactory and adding to ThreadLocal");
            
            sess = factory.openSession( );
            hibernateHolder.set(sess);
        }
        else {logger.debug("Hibernate Session found in ThreadLocal");}
        
        return sess;
    }
}

The SessionFactory is stored as a static variable and the Session is stored in ThreadLocal.  This solves my initial problem of maxing out the connection pool by explicitly closing the Session in the "finally" block of the Struts 2 interceptor.

2 comments: