Open Session in ViewTODO: This page has to be cleaned up, as some people still believe that this pattern creates a dependency between the presentation layer and Hibernate. It does not, see this thread on the forum. One of the patterns that works well with Hibernate is creating a session for the view during the duration of its life. By doing this, the code that renders your view does not need to worry about initializing anything. Your view code can just assume that the Hibernate session has been created, and it can happily go on doing its real work. Here is a simple Filter which offers a thread local Hibernate session to your application and cleans up at the end of the request. The session is instantiated lazily upon first use. Note that this is designed to work with Hibernate2; in order to work with 1.X you will need to change the import and add an addition catch (SQLException).
package org.infohazard.pig.servlet;
import java.io.*;
import javax.servlet.*;
import javax.naming.*;
import net.sf.hibernate.*;
/**
* Filter which manages a ThreadLocal hibernate session. Obtain the session
* by calling Persistance.getSession(). Define the JNDI name of the
* hibernate session factory as an init param to the filter in your web.xml.
*
* @author <a href="mailto:jeff@infohazard.org">Jeff Schnitzer</a>
*/
public class Persistance implements Filter
{
/**
* Filter init param which defines the JNDI name for the hibernate factory
*/
public static final String HIBERNATE_FACTORY_JNDI_PARAM = "hibernateFactory";
/**
* Default value if no init param is set.
*/
public static final String HIBERNATE_FACTORY_JNDI_DEFAULT = "java:/HibernateSessionFactory";
/**
* Holds the current hibernate session, if one has been created.
*/
protected static ThreadLocal hibernateHolder = new ThreadLocal();
/**
*/
protected static SessionFactory factory;
/**
*/
public void init(FilterConfig filterConfig) throws ServletException
{
// Initialize hibernate
try
{
new Configuration().configure();
}
catch (HibernateException ex) { throw new ServletException(ex); }
// As good a place as any to initialize the factory
String factoryJndiName = filterConfig.getInitParameter(HIBERNATE_FACTORY_JNDI_PARAM);
if (factoryJndiName == null)
factoryJndiName = HIBERNATE_FACTORY_JNDI_DEFAULT;
try
{
Context ctx = new InitialContext();
factory = (SessionFactory)ctx.lookup(factoryJndiName);
}
catch (NamingException ex) { throw new ServletException(ex); }
}
/**
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
if (hibernateHolder.get() != null)
throw new IllegalStateException(
"A session is already associated with this thread! "
+ "Someone must have called getSession() outside of the context "
+ "of a servlet request.");
try
{
chain.doFilter(request, response);
}
finally
{
Session sess = (Session)hibernateHolder.get();
if (sess != null)
{
hibernateHolder.set(null);
try
{
sess.close();
}
catch (HibernateException ex) { throw new ServletException(ex); }
}
}
}
/**
* ONLY ever call this method from within the context of a servlet request
* (specifically, one that has been associated with this filter). If you
* want a Hibernate session at some other time, call getSessionFactory()
* and open/close the session yourself.
*
* @return an appropriate Session object
*/
public static Session getSession() throws HibernateException
{
Session sess = (Session)hibernateHolder.get();
if (sess == null)
{
sess = factory.openSession();
hibernateHolder.set(sess);
}
return sess;
}
/**
* @return the hibernate session factory
*/
public static SessionFactory getSessionFactory()
{
return factory;
}
/**
* This is a simple method to reduce the amount of code that needs
* to be written every time hibernate is used.
*/
public static void rollback(Transaction tx)
{
if (tx != null)
{
try
{
tx.rollback();
}
catch (HibernateException ex)
{
// Probably don't need to do anything - this is likely being
// called because of another exception, and we don't want to
// mask it with yet another exception.
}
}
}
/**
*/
public void destroy()
{
// Nothing necessary
}
}
Here is the old version, which is significantly more complicated:
Below is an example of a servlet filter that opens a Hibernate session for each request. This example also uses a Proxy object to wrap the session so that the session is only initialized when it is actually used. By doing this we don't have to worry about wasting database connections on requests that will never use it. In my opinion, this implementation is overcomplicated. I think this was originally written against an older version of Hibernate that obtained JDBC connections in the call to openSession(). More recent versions of Hibernate fetch connections lazily, so the proxy is unnecessary! - Gavin Gavin - Does this apply to Transaction too? I.e if I'm using JTA transactions, would it make sense to avoid the call to transaction.start until needed? It might be nice to avoid starting and stopping a transaction on requests that don't do any db access. Or does Hibernate do that already? Thanks! - Steve
package com.xe.hibernate.example;
import cirrus.hibernate.Session;
import cirrus.hibernate.SessionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.InitialContext;
import javax.servlet.*;
import java.io.IOException;
public class HibernateFilter implements Filter
{
private Log log = LogFactory.getLog(HibernateFilter.class.getName());
private static final String PARAM_SESSION_FACTORY_JNDI = "HIBERNATE_FILTER_SESSION_FACTORY_JNDI";
private static final String PARAM_REQUEST_ATTRIBUTE_ID = "HIBERNATE_FILTER_REQUEST_ATTRIBUTE_ID";
private static final String PARAM_USE_PROXY = "HIBERNATE_FILTER_USE_PROXY";
private static final String DEFAULT_SESSION_FACTORY_JNDI = "java:comp/env/hibernate/SessionFactory";
private static final String DEFAULT_REQUEST_ATTRIBUTE_ID = "HibernateSession";
private SessionFactory sessionFactory;
private String sessionFactoryId;
private String requestAttributeId;
private boolean useProxy = true;
public void doFilter(ServletRequest aRequest, ServletResponse aResponse, FilterChain aChain)
throws IOException, ServletException
{
Session vSession = null;
boolean vSessionCreated = false;
try
{
if (sessionFactory == null)
{
sessionFactory = (SessionFactory) new InitialContext().lookup(sessionFactoryId);
}
// Get the http session id from the request, then we will
// try to get the Hiberate Session from the request. If
// it doesn't exist, then we will create it, otherwise
// we will use the one that already exists.
vSession = (Session) aRequest.getAttribute(requestAttributeId);
if (vSession == null)
{
if (useProxy)
{
vSession = SessionProxy.createProxy(this,sessionFactory);
}
else
{
vSession = sessionFactory.openSession();
if (log.isDebugEnabled())
{
log.debug("Opened hibernate session.");
}
}
aRequest.setAttribute(requestAttributeId, vSession);
vSessionCreated = true;
}
}
catch (Exception exc)
{
log.error("Error opening Hibernate session.", exc);
}
try
{
aChain.doFilter(aRequest, aResponse);
}
finally
{
try
{
if (vSessionCreated)
{
if (vSession != null)
{
// Only try to close the connection if it is open,
// since it might have been closed somewhere else
// by mistake.
if (vSession.isOpen())
{
vSession.close();
if (log.isDebugEnabled())
{
log.debug("Closed hibernate session.");
}
}
}
}
}
catch (Exception exc)
{
log.error("Error closing hibernate session.", exc);
}
}
}
public void init(FilterConfig aConfig) throws ServletException
{
ServletContext vContext = aConfig.getServletContext();
sessionFactoryId = vContext.getInitParameter(PARAM_SESSION_FACTORY_JNDI);
requestAttributeId = vContext.getInitParameter(PARAM_REQUEST_ATTRIBUTE_ID);
String vUseProxy = vContext.getInitParameter(PARAM_USE_PROXY);
if (sessionFactoryId == null)
{
sessionFactoryId = DEFAULT_SESSION_FACTORY_JNDI;
log.info(
"The session factory JNDI location was not specified in the web.xml file."
+ "Using default location."
);
}
if (requestAttributeId == null)
{
requestAttributeId = DEFAULT_REQUEST_ATTRIBUTE_ID;
log.info(
"The request attribute id was not specified in the web.xml file. "
+ "Using default attribute id."
);
}
if (vUseProxy != null)
{
useProxy = vUseProxy.equalsIgnoreCase("true");
}
}
public void destroy()
{
}
}
Below is the source code for the ProxyObject used to wrap the session:
package com.xe.hibernate.example;
import cirrus.hibernate.Session;
import cirrus.hibernate.SessionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SessionProxy implements InvocationHandler
{
private static final Log log = LogFactory.getLog(SessionProxy.class);
private static final Class[] interfaces = new Class[]{Session.class};
private SessionFactory sessionFactory;
public Session session;
public SessionProxy(SessionFactory aSessionFactory)
{
sessionFactory = aSessionFactory;
}
public static Session createProxy(Object aCaller, SessionFactory aSessionFactory)
{
return (Session) Proxy.newProxyInstance(
aCaller.getClass().getClassLoader(),
interfaces,
new SessionProxy(aSessionFactory)
);
}
public Object invoke(Object aProxy, Method aMethod, Object[] aArgs) throws Throwable
{
if (session == null)
{
String vMethodName = aMethod.getName();
if (vMethodName.equals("close"))
{
// We don't need to close a session that was never opened,
// so return...
return null;
}
session = sessionFactory.openSession();
if (log.isDebugEnabled())
{
log.debug("Opened hibernate session.");
}
}
Object vResult = null;
try
{
vResult = aMethod.invoke(session, aArgs);
}
catch (Throwable exc)
{
Throwable vCause = exc.getCause();
if (vCause != null)
{
throw vCause;
}
else
{
throw exc;
}
}
return vResult;
}
}
Here is an example web.xml file showing how to use the HibernateFilter in your web application.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<display-name>HibernateFilter</display-name>
<description>Hibernate Filter Example</description>
<!-- The hibernate filter configuration -->
<context-param>
<param-name>HIBERNATE_FILTER_SESSION_FACTORY_JNDI</param-name>
<param-value>java:comp/env/hibernate/SessionFactory</param-value>
</context-param>
<context-param>
<param-name>HIBERNATE_FILTER_REQUEST_ATTRIBUTE_ID</param-name>
<param-value>HibernateSession</param-value>
</context-param>
<context-param>
<param-name>HIBERNATE_FILTER_USE_PROXY</param-name>
<param-value>true</param-value>
</context-param>
<filter>
<filter-name>HibernateFilter</filter-name>
<filter-class>com.xe.hibernate.HibernateFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HibernateFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<!-- Everything else goes here -->
</web-app>
Please note that this example assumes that the Hibernate SessionFactory has already been initialized somewhere else. Question: How do you handle session disconnection/reconnection? It appears that your session holds a database connection until the end of the request. This limits the number of requests you can process in parallel to the number of connections in your pool, which implies scalability problems. -- ToddJonker
|