HIBERNATE JBoss.org
 |  Register  | 
     
News 
About 
   Feature List 
   Road Map 
Documentation 
   Related Projects 
   External Documentation 
Download 
Forum & Mailinglists 
Support & Training 
JIRA Issue Tracking
Wiki Community Area


Hibernate Public Training Courses


Get Hibernate in Action eBook!


JavaWorld 2003 Finalist


Jolt Award 2004 Winner
      
Documentation > Community Area > Using Hibernate with Tomcat and JNDI

Using Hibernate with Tomcat and JNDI

Preface

Tomcat comes bundled with a read-only JNDI context. This means Hibernate can not bind its SessionFactory to JNDI, even if you specify a name for it in the properties or hibernate.cfg.xml. In this case, you have to create the SessionFactory from a Configuration and store it in your own implementation of a singleton registry. Every execution thread calls this singleton and retrieves the SessionFactory to create a Session. Here's a hibernate.cfg.xml example that prevents SessionFactory JNDI binding:

<hibernate-configuration>

<session-factory>
<!-- Use a Tomcat JNDI datasource -->
<property name="connection.datasource">java:comp/env/jdbc/mydb</property>
<property name="show_sql">false</property>
<property name="use_outer_join">true</property>
<property name="dialect">net.sf.hibernate.dialect.PostgreSQLDialect</property>

<mapping resource="myclasses.hbm.xml"/>

</session-factory>

</hibernate-configuration>

This configuration uses a datasource bound to JNDI (see the Tomcat JNDI howto), but doesn't bind the SessionFactory to JNDI. You have to create and hold it in your own application code.

Usual solution

A good solution for this problem is the Thread Local Session design pattern with a minor modification to replace the JNDI lookup for the SessionFactory:

public class HibernateSession {

private static SessionFactory sessionFactory;
public static final ThreadLocal session = new ThreadLocal();

public static Session currentSession()
throws HibernateException {

Session s = (Session) session.get();
if (s == null) {

// Don't get from JNDI, use a static SessionFactory
if (sessionFactory == null) {

// Use default hibernate.cfg.xml
sessionFactory = new Configuration().configure().buildSessionFactory();

}

s = sessionFactory.openSession();
session.set(s);
}
return s;
}

public static void closeSession()
throws HibernateException {

Session s = (Session) session.get();
session.set(null);
if (s != null) s.close();
}

}

Let Tomcat do the binding

Bertrand Lancelot (blancelot@partner.auchan.com) posted another method to the Hibernate developer list: It is possible to bind the SessionFactory to Tomcats JNDI. Instead of specifying a name in the Hibernate configuration, you have to implement an ObjectFactory and let Tomcat do the binding. Here's a quote from his e-mail:

Tomcat can initialize JNDI names thanks to server.xml with <GlobalNamingResources>, <Resource> and <ResourceParams>, but to configure that, we have to use a class which implements javax.naming.ObjectFactory. There is no class in Hibernate that implements it. That's why we can't configure Tomcat to get a SessionFactory with JNDI. I create this class and after many tests, I had good results to get a SessionFactory and to create Sessions.

import java.io.File;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Hashtable;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.SimpleLog;
import org.apache.commons.logging.LogConfigurationException;

/**
* JndiSessionFactoryImpl to configure in an API Server to initialize
* a SessionFactory in a JNDI context.
*
* Tomcat Configuration in server.xml :
*
* <!-- Global JNDI resources -->
* <GlobalNamingResources>
* ...
* <Resource description="SessionFactory Resource"
* name="HibernateSessionFactory" auth="Container"
* type="net.sf.hibernate.SessionFactory"/>
* <ResourceParams name="HibernateSessionFactory">
* <parameter>
* <name>factory</name>
* <value>JndiSessionFactoryImpl</value>
* </parameter>
* <parameter>
* <name>pathname</name>
* <value>conf/hibernate.cfg.xml</value>
* </parameter>
* </ResourceParams>
* </GlobalNamingResources>
*
* ...
* <!-- Define the Tomcat Stand-Alone Service -->
* <Service name="Tomcat-Standalone">
* <!-- Define the top level container in our container hierarchy -->
* <Engine name="Standalone" defaultHost="localhost" debug="0">
* <Host name="localhost" debug="0" appBase="webapps"
* unpackWARs="true" autoDeploy="true">
* ...
* <Context path="/testjndi" docBase="/testjndi.war" debug="0"
* reloadable="true" crossContext="true" privileged="true" useNaming="true">
* <Logger className="org.apache.catalina.logger.FileLogger"
* prefix="localhost_jndi_log." suffix=".txt"
* timestamp="true"/>
* <ResourceLink name="hibernate/SessionFactory"
* global="HibernateSessionFactory"
* type="net.sf.hibernate.SessionFactory"/>
* </Context>
* </Host>
* </Engine>
* </Service>
*
*/
public class JndiSessionFactoryImpl implements ObjectFactory {

public static final String PATH_NAME = "pathname";
private static SessionFactory sf = null;
private static Log log = null;

static {
try {
log = LogFactory.getLog (JndiSessionFactoryImpl.class);
} catch (LogConfigurationException e) {
System.err.println ("Cannot get a LogFactory instance!");
log = new SimpleLog("log");
}
}

public Object getObjectInstance(Object obj,
Name name,
Context nameCtx,
Hashtable environment)
throws NamingException {

RefAddr addr = ((Reference)obj).get (PATH_NAME);
String content = null;

log.info ("Initializes a Sessionfactory instance");

if (addr != null) {
content = (String)addr.getContent ();
} else {
log.error("parameter " + PATH_NAME + " not found");
}

log.debug ("pathname : " + content);

if (System.getProperty ("api.server.home") == null) {
log.error("property \"api.server.home\" not found!");
}

// Initialize Hibernate configuration with hibernate.cfg.xml :
log.info("Read the hibernate.cfg.xml at : " +
System.getProperty("api.server.home") +
File.separator + content);

if (sf == null) {
try {
String filePath = System.getProperty("api.server.home") "/" + content;
File file = new File(filePath);

sf = new Configuration().configure(file).buildSessionFactory();
log.info ("Sessionfactory successfully initialized!");
} catch (HibernateException e) {
log.fatal ("Cannot get a SessionFactory instance", e);
}
}

return sf;
}

}

I've modified the original code slighty for better readability, I've not tested it. Bertrand Lancelot writes further:

Tomcat Configuration:

  • Put all Hibernate libraries in tomcat/common/lib
  • This class use a new property "api.server.home" which must contain the API server home. Thus add =-Dapi.server.home="{%CATALINA_HOME%}"=in the file catalina.bat (or cataline.sh).
  • Put hibernate.cfg.xml in tomcat/conf.
  • Put all mappings and JAVA BEANs and the factory JndiSessionFactoryImpl.class in tomcat/common/classes
  • If you encounter an UnsupportedOperationException, be sure to comment out the hibernate.connection.username and hibernate.connection.password entries in the hibernate.properties file (see FAQ for more details)
  • Finally, modify tomcat/conf/server.xml

Problems

Bertrand Lancelot further states that he had problems with his classloader with the ObjectFactory approach. His application (living in its own context) couldn't find the persistent classes he declared in his mapping. Exceptions were like:

net.sf.hibernate.MappingException: No persister for: bo.Reception
at net.sf.hibernate.impl.SessionFactoryImpl.getPersister(SessionFactoryImpl.java:444)
at net.sf.hibernate.impl.SessionImpl.getPersister(SessionImpl.java:2249)

This was expected, because Tomcat "sometimes" (actually, this is the difference between the standard JDK classloader concept and the "broken" Servlet specification) has a different delegation strategy for its classloaders:

The classloader at the common/lib and common/classes level is not the same as the context classloader, responsible for a specific WEB-INF/lib and WEB-INF/classes. You would expect that every loadClass(), new and Class.forName() call in your application results in a search at the current level and then a delegation to the upper level classloader if no class can be found. This is not the case! The default is to first search with the parent classloader and if he can not find the class, with its parent classloader. This chain of responsibility continues up to the top of the classloader delegation hierarchy. Only if no classloader in the chain can find your requested class, your context classloader will get a chance to try it.

One way to modify this behaviour (and you usually want it different) is to work with the <loader> directive in Tomcats server.xml (in the declaration of each context):

<Loader checkInterval="1" delegate="false"/>

This turns on auto-reloading of classes (one second check time), useful in development. It also prevents the delegation to the parent classloader: The context classloader gets the first chance to find the class for your application. The default of this attribute (true or false) seems to vary from Tomcat version to version. Much of the classloading problems in Tomcat (the broken commons-logging and log4j, for example) stems from this simple fact.

I haven't found a solution for the problem of Bertrand Lancelot (in fact, I have no time to try it) and I also don't see an advantage to the SessionFactory binding by Tomcat instead of the singleton HibernateSession class. You'll need such a class most of the time anyway, it's a good place for Configuration and SessionFactory.

If anyone has some suggestions, improvements or a solution for the classloading problem, feel free to edit this page. The obvious solution is to place persistent classes at the same level as the Hibernate libraries (ie. in common/lib or common/classes), but this is not desireable in a multi-host environment and certainly can not be recommended for various reasons.

Some links:

Related: SessionFactory Handling with the Spring Framework

The Spring Framework offers an easy way to configure and handle SessionFactory instances, be it local or from JNDI. You can switch between various SessionFactory sources without changing a single line of Java code. Most importantly, there is no need to keep a SessionFactory in JNDI - this doesn't add any value outside of EJBs. Locally defined resources within a Spring application make deployment much easier, avoiding any container-specific configuration hassle. This applies to Tomcat in particular but also to JBoss or WebLogic. See part 6 of Data Access with the Spring Framework for a discussion of this.

Furthermore, Spring provides a convenient pre-built implementation of the Thread Local Session pattern, integrated with its transaction management infrastructure via a Hibernate-specific strategy. Additionally, there's special support for Thread Local Sessions in the JTA strategy too, enabling proper cache handling without any container-specific setup. Spring also offers IoC-style templating and AOP solutions for simplified Hibernate Session handling within DAOs.

Note that Spring adopts a very lightweight approach. You can use a lot of features respectively classes in a library style, as everything is designed as a set of reusable JavaBeans. Don't be discouraged by the fact that Spring can serve as full application framework too. You're invited to review and leverage the Spring approach, no matter to what extent, before deciding to take the effort and risk of building such infrastructure in-house.

Data Access with the Spring Framework

Thread Local Session

      

coWiki