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.

Friday, June 10, 2011

Hibernate - Part 6 - c3p0 connection pooling

In an earlier post, I explained a technique for storing a Hibernate SessionFactory in the ServletContex.  The objective was to create the Hibernate SessionFactory once and add it to the ServletContext so that it is available for the life of the application.

This solution seemed sufficient for my needs and I moved on to other issues.  However, I soon noticed my application would hang when left idle overnight.  It wasn't immediately clear what caused this behavior but as I looked through the logs I found the following error.

WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 0, SQLState: 08S01
ERROR org.hibernate.util.JDBCExceptionReporter - The last packet successfully received from the server was 49,245,917 milliseconds ago.
The last packet sent successfully to the server was 49,245,917 milliseconds ago. is longer than the server configured value of 'wait_timeout'.
You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
I found an informative blog post explaining the root of this problem.  The author explains that MySQL is dropping the connection after the idle timeout expires and by default Hibernate is not able reconnect on its own.  The solution is to use connection pooling, something I was planning on implementing eventually anyway.  The Hibernate documentation addresses this issue as follows:
Hibernate's own connection pooling algorithm is, however, quite rudimentary. It is intended to help you get started and is not intended for use in a production system, or even for performance testing. You should use a third party pool for best performance and stability. Just replace the hibernate.connection.pool_size property with connection pool specific settings. This will turn off Hibernate's internal pool. For example, you might like to use c3p0.
 Adding c3p0 connection pooling was as easy as adding the jar to my classpath and adding the following into my hibernate.cfg.xml

        <!-- Use the C3P0 connection pool provider -->
        <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">10</property>
        <property name="hibernate.c3p0.timeout">1800</property>
        <property name="hibernate.c3p0.max_statements">50</property>
        <property name="hibernate.c3p0.idle_test_period">900</property>

And voila, we now have connection pooling.  I was no longer getting the MySQL errors noted at the beginning of this post.

Unfortunatly, I was about to run into another set of issues related to using connection pooling and my existing strategy for creating the Hibernate SessionFactory once and adding it to the ServletContext.  More on that in my next post.