This is my understanding concerning how the Tomcat / Hibernate / database environment works. This is based on a post I made to the Tomcat Users mailing list. In this wiki article, I'll work through both Tomcat - managed database connection pooling and Hibernate - managed database connection pooling.
I use primarily the following two environments when developing concepts and testing ideas.
Component |
Version |
OS |
Fedora 11 32 bit |
JDK/JRE |
1.6.0_20 |
Tomcat |
6.0.26 |
MySQL |
5.1.41-2 |
IDE |
NetBeans 6.8 |
Hibernate |
3.2.5 ga |
|
|
OS |
Windows/XP Professional SP 4 32 bit |
JDK/JRE |
1.6.0_20 |
Tomcat |
6.0.26 |
MySQL |
5.1.31 |
IDE |
NetBeans 6.8 |
Hibernate |
3.2.5 ga |
In order to work through the two configurations, I've chosen to use the NetBeans Hibernate Tutorial. The original tutorial does not make any use of database pooling. Modifications to the tutorial will be shown below to use either Tomcat's database pooling via JNDI or Hibernate's provided C3P0 database pooling.
Before proceeding, either work through or download the Netbeans Hibernate Tutorial. This will ensure that the project is set up properly so that modifications can be easily made.
Per Tomcat documentation, I've placed mysql-connector-java-5.1.11-bin.jar in Tomcat's lib directory. This makes the JDBC driver available to Tomcat in order to set up database pooling. No Hibernate jars should be placed in Tomcat's lib directory.
All Hibernate jars are located in WEB-INF/lib. NetBeans builds the war file correctly, so no jar files have to be copied.
Per Tomcat documentation, a context.xml file in META-INF is created.
<?xml version="1.0" encoding="UTF-8"?> <Context antiJARLocking="true" path="/DVDStore"> <Resource auth="Container" driverClassName="com.mysql.jdbc.Driver" maxActive="30" maxIdle="10" maxWait="10000" name="jdbc/sakila" password="*****" type="javax.sql.DataSource" url="jdbc:mysql://localhost/sakila" username="*****"/> </Context> |
Obviously, replace the asterisks with the appropriate username and password. Replace the database url with your appropriate database.
In WEB-INF/web.xml you'll need to add the following lines.
<resource-ref> <description>This is a MySQL database connection</description> <res-ref-name>jdbc/sakila</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> |
Again, this is all boilerplate configuration and is documented on the Tomcat documentation web site. In particular, please note that the res-ref-name in web.xml must match the name attribute of the resource node in the context.xml file.
So far, this has all been more or less boilerplate Tomcat configuration. The next step is to get Hibernate to talk to Tomcat's database connection pool as opposed to its own connection pool or a direct connection.
After searching a bit on the Internet, I found that the following connection information works.
In hibernate.cfg.xml located in WEB-INF/, put the following.
<!-- using container-managed JNDI --> <propertyname="hibernate.connection.datasource"> java:comp/env/jdbc/sakila </property> |
Note that jdbc/sakila matches the web.xml resource-ref-name, which matches the name attribute in context.xml. java:comp/env/ is the namespace to look up the jdbc reference.
No other connection or pooling information should be present in your hibernate.cfg.xml file. Note, you still need the database dialect properties in order for Hibernate to function.
If you are using Netbeans as your IDE, the hibernate.cfg.xml file will be found in <project-name>/src/java by default.
One of the nice things about Hibernate is that it shields your application code from database connection particulars. You can change from Tomcat JNDI pooling to Hibernate pooling, to a direct connection all without changing the underlying application code. In particular:
Creating the SessionFactory
// SessionFactory from standard hibernate.cfg.xml file try { sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); } catch (Throwable ex) { // Log the exception. log.fatal("Initial SessionFactory creation failed.", ex); throw new ExceptionInInitializerError(ex); } |
and getting the SessionFactory
public static SessionFactory getSessionFactory() { return sessionFactory; } |
work with no modifications.
For my application, both of these code snippets live in HibernateUtil.java which NetBeans generates for you. My code also has an mbean for monitoring Hibernate.
You can then call this code from a class that implements ServletContextListener. This will then create the session factory, or throw an exception if this fails at application startup. It's also handy for cleaning up resources when the application is shut down. In my case, I use this to unregister the monitoring mbean.
If you look at your logs, you may see the following warning:
INFO SessionFactoryObjectFactory:82 - Not binding factory to JNDI, no JNDI name configured |
This is due to the fact that Hibernate can bind a session factory to a JNDI name, but none is configured. This is not the JDBC resource. This facility probably exists to make sharing session factories easier, and to clean up calls like the following:
Session session = HibernateUtil.getSessionFactory().openSession(); |
Tomcat provides a read-only InitialContext, while Hibernate requires read-write in order to manage multiple session factories. Tomcat is apparently following the specification for unmanaged containers. If you want to bind the session factory to a JNDI object, you'll either have to move to a managed server (Glassfish, JBoss, etc.), or search on the Internet for some posted work-arounds.
The recommendation from the Hibernate documentation is to just leave out the hibernate.session_factory_name property when working with Tomcat to not try binding to JNDI.
I've tried this only briefly, since I always rely on container managed database connections. However, it appears to work cleanly, and may be useful when frequently migrating between servlet containers.
According to the Tomcat classloader documentation, Tomcat makes jar files available to your application in the following order:
In particular, the following two pieces of information should be noted.
Based on the above, the MySQL connection jar will need to be placed in the WEB-INF/lib directory.
Approach Using NetBeans
In order to do this with NetBeans, add the jar file to the project. An easy way to do this on a per-project basis is the following:
Please note that the above method will cause all sorts of problems if you are sharing this project or using SCM. Some more portable ways of adding third party jars include adding the jar as a NetBeans library, creating a library project, or using Maven and placing the dependency in the POM. All of these approaches are beyond the scope of this article.
Do not also copy the JDBC driver jar to $CATALINA_HOME/lib.
Hibernate ships with the C3P0 connection pooling classes, so as long as the Hibernate jars are in WEB-INF/lib directory (which they should be), they should be available.
Since Hibernate will be managing both the connection and the database pooling, Hibernate will have to be configured with that information.
In hibernate.cfg.xml, both connection information and connection pooling information are needed. Here are the snippets of that file.
<!-- connection information --> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.connection.url"> jdbc:mysql://localhost/sakila </property> <property name="hibernate.connection.username">******</property> <property name="hibernate.connection.password">******</property> <!-- database pooling information --> <property name="connection_provider_class"> org.hibernate.connection.C3P0ConnectionProvider </property> <property name="c3p0.minPoolSize">5</property> <property name="c3p0.timeout">1000</property> |
There are probably other properties to configure, but that should get things up and running. As always, change the database properties to reflect your particular application.
Tomcat managed database connections:
Hibernate managed database connections: