Hibernate 3.1 Used with Tomcat 5.5.x
There have been a number of changes in Tomcat configuration as the Tomcat version advanced from Tomcat 5.0 through version 5.5.x. These changes have affected Tomcat JNDI setup, which in turn has affected Hibernate, assuming you wish your Hibernate installation to take advantage of the DataSource facility made available from Tomcat JNDI.
Part 1: JNDI Setup for Tomcat 5.5.x
There have been a number of changes in Tomcat configuration as the Tomcat version advanced from Tomcat 5.0 through version 5.5.x. These changes have affected Tomcat JNDI setup, which in turn has affected Hibernate, assuming you wish your Hibernate installation to take advantage of the DataSource facility made available from Tomcat JNDI.
In addition, as the Hibernate version advanced from 3.0 through 3.1, there was a significant simplification (and therefore a change) in how you obtain a thread-safe Hibernate session.
Tomcat is a hybrid sort of environment (neither fish nor fowl) from the standpoint of Hibernate. Tomcat provides a JNDI and DataSource, unlike a standalone application. The DataSource facility should facilitate portability among application servers. But like a standalone environment, Tomcat provides no Transaction Manager. Hence your code must use the Hibernate Transaction Manager, as it would in a standalone application environment.
We found it difficult to obtain any reasonably complete description of this pair of component versions being used together - we could not find the information on the web, not even on the Hibernate.org site (http://www.hibernate.org). In several places we found older information, but it no longer seems very useful because of the changes that have taken place. The Hibernate 3.0 Reference Documentation opened with a very useful chapter on Hibernate/Tomcat integration (based on Tomcat 4.1), but that chapter has been removed from the current Hibernate 3.1 Reference Documentation. We hope this set of three articles will help you to use the current versions of Hibernate and Tomcat on your project.
The Context.xml File
In earlier versons of Tomcat, you configured Tomcat JNDI via the server-wide configuration file, server.xml. You included multiple <ResourceParams> elements within this file, one <ResourceParams> element per DataSource. In Tomcat 5.5, you no longer use this server-wide file to configure JNDI. Instead, you place a per-application file named (exactly) Context.xml into your META-INF directory, for instance C:Tomcat 5.5webappsBasicWebMETA-INF Context.xml. (You may find references on the web indicating this file should be named after your application, for instance YourApp.xml. That is not correct - you should use the unvarying filename Context.xml.)
Here is a portion of a screenshot showing the placement of Context.xml in your Tomcat directory hierarchy:
In your application's Context.xml file, you should include one <Resource> element per DataSource. This <Resource> element has a set of attributes defining the DataSource, but no sub-element. Here is the complete text of Context.xml for an application which only requires a single DataSource (certainly not an unusual condition):
<?xml version="1.0" encoding="UTF-8"?> <Context path="/BasicWeb" reloadable="true" docBase="C:Tomcat 5.5webappsBasicWeb" workDir="C:Tomcat 5.5webappsBasicWeb"> <Resource name="jdbc/Sampdb" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="sam" password="sam123" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/sampdb?autoReconnect=true"/> </Context>
The above <Resource> element has the effect of creating a Tomcat JNDI DataSource entry. The JNDI and this JNDI entry are created when the Tomcat server is started. This JNDI DataSource entry is no different from such an entry in an application server from the standpoint of retrieving information from the JNDI. (However, the Tomcat JNDI is read-only after the Tomcat startup cycle is completed. Neither your custom application nor Hibernate can add or revise entries in this JNDI during ongoing Tomcat operation.)
Incidentally, the several articles in this series happen to be using MySQL as the datastore, as you can see in the above <Resource> element.
Not only can Hibernate use the Tomcat JNDI, but your raw JDBC can access the JNDI as well, in the same manner as you can access a DataSource from a JNDI in an application server. Here are five lines of code from a servlet which obtains a JNDI connection from the above JNDI <Resource> element, and employs raw JDBC (not Hibernate) to perform queries against the database.
InitialContext initCtx = new InitialContext(); DataSource ds = (DataSource)initCtx.lookup("java:comp/env/jdbc/Sampdb"); Connection conn = ds.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from car");
We would like to present the full text of the servlet, but before we do so, it may be worth stating our objective in this example of running code and in all the examples of running code that follow: Our objective is to teach and illustrate how Hibernate 3.1 and Tomcat 5.5 function together (which is after all the title of this series). Code which shows its innards because it is meant to be easy to understand, like this code, is not the same as commercial quality code you intend to deploy - which would include complete exception handling and other practices such as use of the MVC pattern which are irrelevant to our purposes. (You would certainly use a JSP, not a servlet, as your View component. But introducing even part of an MVC pattern would blur the focus from where we wish it to stay - the intersection of Hibernate and Tomcat.)
Here, then, is the complete text of our first servlet example:
package TomcatJNDI; import java.io.IOException; import java.io.PrintWriter; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Retrieval extends HttpServlet { /** * Constructor of the object. */ public Retrieval() { super(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet Which Accesses JNDI</TITLE></HEAD>"); out.println(" <BODY>"); try { InitialContext initCtx = new InitialContext(); DataSource ds = (DataSource)initCtx.lookup("java:comp/env/jdbc/Sampdb"); Connection conn = ds.getConnection(); out.println("<br><br>"); out.println("Connection from DataSource successfully opened!<br>"); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from car"); while (rs.next() ) { String onerow = "Car manufacturer: " + rs.getString("manufacturer") + ," model: " + rs.getString("model") + ," year: " + rs.getString("year") + "<br>"; out.println(onerow); } rs.close(); stmt.close(); conn.close(); initCtx.close(); out.println("<br><br>"); out.println("Connection from DataSource successfully closed!<br>"); } catch(Exception e) { out.println("<br><br>"); out.println("Connection from DataSource NOT successfully opened!<br>"); out.println(e.getMessage() + "<br>"); } out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } public void init() throws ServletException { // Put your code here } } //End class Retrieval
Note that Tomcat, like a normal application server, is actually providing database connection pool support without the servlet having to ask for such support or even knowing it exists. In the case of Tomcat, the connection pool support is provided by the DBCP component. In the <Resource> xml element shown earlier, the maxActive="100" attribute signified a maximum of 100 active connections should be supported in the pool.
This completes the first article in this series, which has focused on Tomcat 5.5.x JNDI setup. The Context.xml file shown above, without a single character needing to be changed, will also support Hibernate. In fact, such a file is absolutely necessary in order to utilize Tomcat JNDI from Hibernate.
In the next article, we will document Hibernate 3.1 configuration under Tomcat 5.5.x, and show a simple Hibernate-based servlet which uses the above Context.xml and JNDI setup. The Hibernate-based servlet in this article will perform queries against the MySQL database.
In the third article in the series, we will perform inserts into the database within the boundaries of a Hibernate transaction, again using the above JNDI setup.
Part 2: Tomcat Servlets that Employ Hibernate for Database Queries
In this second article, the example servlet will use Hibernate, not raw JDBC, to access the MySQL database, but the same Context.xml file will continue to be used, without change. Hence Hibernate will take advantage of the same JNDI DataSource as the raw JDBC used in the previous article.
Web.xml
Let's begin by looking at web.xml. (We assume you know that web.xml identifies and configures the components of any Java web application, and that you understand the format of this file.)
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <listener> <listener-class>tomcatJndi.HibernateAppListener</listener-class> </listener> <servlet> <servlet-name>Retrieval</servlet-name> <servlet-class>tomcatJndi.Retrieval</servlet-class> </servlet> <servlet> <servlet-name>RetrieveViaHibernate</servlet-name> <servlet-class>tomcatJndi.RetrieveViaHibernate</servlet-class> </servlet> <servlet> <servlet-name>InsertViaHibernate</servlet-name> <servlet-class>tomcatJndi.InsertViaHibernate</servlet-class> </servlet> <servlet-mapping> <servlet-name>Retrieval</servlet-name> <url-pattern>/Retrieval</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>RetrieveViaHibernate</servlet-name> <url-pattern>/RetrieveViaHibernate</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>InsertViaHibernate</servlet-name> <url-pattern>/InsertViaHibernate</url-pattern> </servlet-mapping> </web-app>
The above represents the entire contents of web.xml for this three-part series. It contains three servlets:
- Retrieval - the servlet which employed raw JDBC in the previous article.
- RetrieveViaHibernate - the servlet in the present article which queries the same MySQL database as the first article, but now uses Hibernate.
- InsertViaHibernate - a servlet in the third and final article which performs inserts into the database within the boundaries of a transaction obtained from Hibernate.
In addition, near the top of web.xml is an xml listener element, which in this case defines a ServletContextListener. The listener is a class that will be instantiated when the web application is being readied to process requests (for instance at Tomcat server startup). Specifically, the contextInitialized() method of this class will be called to do whatever we want it to do before any request is processed by this application. For instance, before any of the three servlets shown in this web.xml is called for the first time, contextInitialized() will be fired for our application. This method will not be fired again unless and until the application is reloaded, or Tomcat is re-started.
ServletContextListener
In our case, what we want the ServletContextListener to do is to load our HibernateUtil class, so that the latter will instantiate a static Hibernate session factory. As a result, the Hibernate session factory will already be available by the time Http requests are received by any of our three servlets. Let's look at the code for our designated listener, tomcatJndi.HibernateAppListener:
package tomcatJndi; import javax.servlet.ServletContextListener; import javax.servlet.ServletContextEvent; import org.apache.log4j.Logger; import org.hibernate.Session; public class HibernateAppListener implements ServletContextListener { /* Application Startup Event */ public void contextInitialized(ServletContextEvent ce) { Logger loggerConnect = Logger.getLogger("Connect"); try { loggerConnect.debug("In HibernateAppListener.contextInitialized"); Class.forName("tomcatJndi.HibernateUtil").newInstance(); loggerConnect.debug("In HibernateAppListener, Class.forName for tomcatJndi.HibernateUtil successful"); } catch (Exception e) { loggerConnect.debug("In HibernateAppListener, Class.forName for tomcatJndi.HibernateUtil throws Exception"); } } /* Application Shutdown Event */ public void contextDestroyed(ServletContextEvent ce) {} }
Note the Class.forName(...).newInstance() method call above, which seeks to ensure the designated class (tomcatJndi.HibernateUtil) is loaded, hence that the static initializer in HibernateUtil is invoked. We need to look at HibernateUtil.
HibernateUtil
package tomcatJndi; import org.hibernate.*; import org.hibernate.cfg.*; import org.apache.log4j.Logger; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { Logger loggerRoot = Logger.getRootLogger(); Logger loggerConnect = Logger.getLogger("Connect"); loggerRoot.debug("In HibernateUtil try-clause"); loggerRoot.error("In HibernateUtil try-clause"); loggerConnect.debug("In HibernateUtil try-clause via loggerConnect DEBUG*****"); // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
Note above where the static SessionFactory (a singleton) is instantiated by buildSessionFactory(). The interface org.hibernate.SessionFactory represents a Hibernate factory from which our code can obtain org.hibernate.Session objects. The Hibernate Session is the vehicle by which our applications can interact with Hibernate, and ask it to perform any of its functions.
It should be noted the class HibernateUtil is simply copied from the Hibernate.org web site (http://www.hibernate.org). Previous versions of the HibernateUtil class available from that site (with earlier versions of Hibernate) were more complex, incorporating a ThreadLocal instance. On the current Hibernate.org site, you can find the document "Hibernate Sessions and Transactions" ( http://www.hibernate.org/42.html), which includes the following wording:
Note: There are many variations of much more complex HibernateUtil classes floating around the net. However, for Hibernate 3.1, the code shown above is the only code that is needed. Every other HibernateUtil is obsolete for Hibernate 3.1.
Let's say your servlet is limited to some kind of database query, i.e. it doesn't update the database in any way. Then it could utilize the Hibernate SessionFactory obtained from HibernateUtil in the following way:
Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); ...[retrieve desired results via a Hibernate query] session.getTransaction().commit(); //Automatically closes session
Note even a simple query utilizing SessionFactory.openSession() should use a Hibernate transaction, and commit the transaction, according to the Hibernate reference documentation. We are choosing to follow this recommendation - it certainly hurts nothing.
Say you have another servlet which employs Hibernate to perform some kind of update to the database. Then your code might take the following form (basically identical to the above):
Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); Car bigSUV = new Car(); bigSUV.setManufacturer("Ford"); bigSUV.setModel("Expedition"); bigSUV.setYear(2005); Driver jill = getDriverByName("Jill"); addCarToDriver(jill, bigSUV); session.getTransaction().commit(); //Automatically closes session
Hibernate.cfg.xml
The method call org.hibernate.cfg.Configuration().configure() in HibernateUtil.java looks for the Hibernate configuration file hibernate.cfg.xml in the classpath. So we must look at that file next. It should be placed in the Tomcat application's classpath in the WEB-INF/classes directory or in a subdirectory of this directory. Here is a screenshot showing the placement of hibernate.cfg.xml (alongside the log4j.properties file, which also needs to be in the classpath).
And here is the full text of the Hibernate configuration file that we are using in this article series.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Use a Tomcat 5.5 JNDI datasource --> <property name="connection.datasource"> java:comp/env/jdbc/Sampdb </property> <property name="show_sql">true</property> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="current_session_context_class">thread</property> <property name="cache.provider_class"> org.hibernate.cache.NoCacheProvider </property> <mapping resource="tomcatJndi/Car.hbm.xml"/> <mapping resource="tomcatJndi/Driver.hbm.xml"/> </session-factory> </hibernate-configuration>
We need to comment not only about what is in this configuration file, but also about certain things that are not in the file. The first thing to notice in this file is <property name="connection.datasource">. This is where Hibernate is directed to utilize the existing JNDI datasource which we configured in Context.xml in the first article. If we were not using the JNDI entry, we would need entries in hibernate.cfg.xml similar to the following (and the above connection.datasource entry would need to be removed from the file):
<property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/sampdb</property> <property name="connection.username">root</property> <property name="connection.password">sammy11</property>
Next, notice the entry <property name="current_session_context_class"> is set to thread. This entry basically allows the new, simplified version of HibernateUtil shown above to be used, so that we don't have to maintain our own ThreadLocal to provide thread safety to our Hibernate Session object.
The first item we wish to point out that you don't see in this file is an xml attribute rather than a sub-element. Specifically, you don't see the name attribute on the session-factory element. What you don't see is anything like the following:
If the name attribute were present - as you would expect it to be in an application server environment - then, when the static initializer in HibernateUtil invoked BuildSessionFactory() in this line of code shown earlier:
what would happen is that Hibernate would seek to install the session factory entry into JNDI. This is fine in an application server, but not in the Tomcat readonly version of JNDI. (An exception would be thrown.) In an application server, there would probably be a second change to the version of HibernateUtil shown earlier, namely, the getSessionfactory() method would retrieve the SessionFactory reference from JNDI, rather than from the static variable which it uses in the version we have shown.
Here are several other properties you don't see in this configuration file:
- transaction.factory_class - the default value of JDBCTransactionFactory is automatically being applied to this configuration. If you wished, you could include the transaction.factory_class property, explicitly giving it the (default) value JDBCTransactionFactory. In an application server environment you could include this property, giving it the value JTATransactionFactory instead. You cannot normally assign the value JTATransactionFactory with Tomcat (unless you used a third party JTA implementation library which is not distributed as part of Tomcat).
- jta.UserTransaction - in an application server environment, you would normally set this to java:comp/UserTransaction, enabling JNDI lookup of a UserTransaction. For the reason stated above, you cannot do this with Tomcat.
- transaction.manager_lookup_class - in an application server environment, this could explicitly identify the provider of the JTA implementation. Again, this would not normally be useful with Tomcat.
Incidentally, simply placing the jar file jta.jar into your Tomcat context classpath does not give you JTA transaction support. It gives you the definition of the interface UserTransaction, but not an implementation which conforms to this interface. The implementations are available from the application servers, or from a third party open source implementation such as JOTM (but this JTA implementation is not distributed with Tomcat).
Hibernate Mapping Files
In the configuration file Hibernate.cfg.xml shown above, two mapping files are listed:
- tomcatJndi/Car.hbm.xml
- tomcatJndi/Driver.hbm.xml
These mapping files must be present in the application's classpath, in the indicated package directory under WEB-INF/classes, as shown in the following screenshot:
As you can see, a particular mapping file is expected to be in the same directory as the corresponding Java class, such as Car.hbm.xml and Car.class.
Before we can show you the contents of our pair of mapping files, we need to explain our very simple data model and application. Our application is concerned with a small family - a husband, Jack; a wife, Jill; and their son, James - who own four cars among them. The reason they own four cars is that one of the cars, a sporty car owned by James, is often in the shop for repairs. James has a tendency to have accidents for which others - never he - is responsible, and the accidents amazingly tend to occur when he is driving that specific car. Note that no member of this family is permitted to drive each other's cars - the attachment to their own car is too great. As a result, there is a one-to-many association between a Driver and the Cars that driver may drive. (In most families, the association would probably be many-to-many, but in the family in our example it is one-to-many.) Nothing stops this family from conceivably purchasing additional cars, but due to the prevailing family dynamics in their family, no member will ever drive each other's cars.
The result of the above data model is that the Driver.hbm.xml file must reflect the collection of cars a driver may potentially drive. There is no similar need for a collection in Car.hbm.xml. Thus Driver.hbm.xml will be the slightly more complex of the two mappng files. Let's look at the simpler file, Car.hbm.xml, first.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="tomcatJndi.Car" table="CAR"> <id name="id" column="CAR_ID" type=â''longâ''> <generator class="native"/> </id> <property name="manufacturer"/> <property name="model"/> <property name="year" type="integer"/> </class> </hibernate-mapping>
Let's look at the corresponding Java source file:
package tomcatJndi; public class Car { private Long car_id; private String manufacturer; private String model; private int year; public Car() {} public Long getId() { return car_id; } private void setId(Long car_id) { //Note private visibility this.car_id = car_id; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } }
And here is the MySQL create table statement. Each car record must contain the foreign key which points to the owner/driver:
create table car( car_id bigint(20) PRIMARY KEY not null auto_increment, manufacturer varchar(40), model varchar(40), year int, fk_driver_id bigint(20), FOREIGN KEY (fk_driver_id) REFERENCES driver (driver_id) )
As stated above, the Driver.hbm.xml mapping file (and corresponding Java source file Driver.java) is slightly more complex, due to the requirement for the one-to-many mapping to the set of Cars the particular Driver owns and drives.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="tomcatJndi.Driver" table="DRIVER"> <id name="id" column="DRIVER_ID" type="long"> <generator class="native"/> </id> <property name="name"/> <property name="age" type="integer"/> <set name="carsOwned" table="CAR"> <key column="fk_driver_id" /> <one-to-many class="tomcatJndi.Car"/> </set> </class> </hibernate-mapping>
The <set> element in the above file represents the Java collection type, set. This xml element and its subelements specify that the table car contains a foreign key column, fk_driver_id, which references the Driver who is on the one side of the one-to-many relationship.
Here is the corresponding Java source file:
package tomcatJndi; import java.util.Set; public class Driver { private Long driver_id; private String name; private int age; private Set carsOwned; public Driver() {} public Long getId() { return driver_id; } private void setId(Long driver_id) { //Note private visibility this.driver_id = driver_id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Set getCarsOwned() { return carsOwned; } public void setCarsOwned(Set carsOwned) { this.carsOwned = carsOwned; } }
And here is the MySQL create table statement:
create table driver( driver_id bigint(20) PRIMARY KEY not null, name varchar(40), age int(11) )
Hence we have set up the required one-to-many relationship between a Driver and the Cars driven by the Driver.
Finally, here is the example servlet which uses Hibernate to query this pair of tables:
package tomcatJndi; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import java.util.Iterator; import java.util.Set; import org.apache.log4j.Logger; import org.hibernate.SessionFactory; import org.hibernate.Session; import org.hibernate.Query; import org.hibernate.cfg.Configuration; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class RetrieveViaHibernate extends HttpServlet { public RetrieveViaHibernate() { super(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is BRAND NEW "); out.print(this.getClass()); out.println(," using the GET method<br>"); doQuery(out); //Employ Hibernate to access both Driver and Car tables out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } private void doQuery(PrintWriter out) { System.out.println("In doQuery"); try { // This step will read hibernate.cfg.xml and prepare hibernate for use Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); String SQL_QUERY ="from Driver driver"; Query query = session.createQuery(SQL_QUERY); for(Iterator it=query.iterate(); it.hasNext(); ) { Driver driver=(Driver)it.next(); out.println("Driver name: " + driver.getName() + ," " ); out.println("Driver age: " + driver.getAge() + "<br>" ); Set carsOwned = (Set)driver.getCarsOwned(); for (Iterator itOwned = carsOwned.iterator(); itOwned.hasNext(); ) { Car carOwned = (Car)itOwned.next(); out.println(" Manufacturer: " + carOwned.getManufacturer() + ," " ); out.println("Model: " + carOwned.getModel() + ," " ); out.println("Age: " + carOwned.getYear() ); out.println("<br>"); } } session.getTransaction().commit(); //Automatically closes session } catch(Exception e) { System.out.println(e.getMessage()); } finally{ } } }
Note above where this servlet obtains the current Session from the SessionFactory in HibernateUtil, and begins a Hibernate transaction, and near the end of the routine, where it commits the Hibernate transaction, even though only a query (not a database update) is being performed.
There are a couple of object-oriented functions which Hibernate is performing in this servlet example. First, observe that unlike the raw JDBC in the first article, the Hibernate database query above is actually returning instances of the Driver and Car classes, as defined in Driver.java and Car.java and the corresponding hbm.xml mapping files. The above code is not returning raw ResultSets.
Second, take a look at the query in the above code: "from Driver driver" (the "select *" syntax is assumed, and not needed). This query says nothing about the Car table, but the applicable Cars are being returned. Actually, the word "Driver" in this query doesn't refer to the Driver table - it refers to the Driver Java class, which does include a Collection of the Cars owned and driven by a given Driver. Hence the query directly returns the Java object instances you wish to work with - there is no need to convert from ResultSets to the target objects. Moreover, the reference from one Java object type (Driver) to the referenced collection of Java objects (Car objects) is pre-populated for you.
There is one further point we wish to make about the above servlet. Notice the following line of code near the top of the method doQuery():
Session session = HibernateUtil.getSessionFactory().openSession();
We could have chosen the following alternative syntax - but we would never choose to do so with Tomcat:
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Why would we never use this alternative syntax with Tomcat? The difference between openSession() and getCurrentSession() is that the former, each time it is used, provides a brand new Hibernate Session. That is exactly what we want in our servlet. In contrast, getCurrentSession attempts to associate a Hibernate Session with a specific thread (the Singleton-per-Thread pattern), which Hibernate achieves via the use of an embedded (hidden) ThreadLocal. Unfortunately, Tomcat maintains a thread pool, and re-uses a given thread after a particular Http request is finished with it. Hence a brand new Http request can receive a previously used thread, which already happens to have a Hibernate Session associated with it (via the ThreadLocal), and getCurrentSession() may by chance receive an unrelated Hibernate Session when it ought to receive a brand new one. We may have a new Http session, and logically a new thread, but physically be re-using an existing thread. In this way, Tomcat 5.5.x and Hibernate 3.1 can confuse each other.
We choose to bypass the issue entirely by using SessionFactory.openSession, and avoiding the use of SessionFactory.getCurrentSession in a Tomcat environment.
The conflict we described above is readily testable. Setup a Tomcat 5.5 environment allowing only a small number of concurrent threads - say 3 or 4. (You accomplish this via an entry in Tomcat Rootconfserver.xml, namely by setting the maxThreads attribute of the applicable <Connector> element in this file to 3 or 4.) Create a couple of distinct, simplistic business transactions (conversational transactions or long-running transactions) which span Http requests. Attempt to preserve information in a ThreadLocal - any information â'"it doesnâ'™t need to have anything to do with Hibernate - and do some tracing/logging in which you display the thread ID. You will see Tomcat thread pooling eventually recycle thread IDs to another business transaction, allowing inappropriate access to the ThreadLocal contents to take place.
You may wish to do some research on the ThreadLocal facility.
That concludes this fairly lengthy second article. It has shown you how to configure Tomcat 5.5 and Hibernate 3.1 for use with each other, and how to perform object-oriented queries in this environment.
In the third article we will show you how to invoke database updates in this environment, and we will have something to say about multi-Http-Request business transactions - the Shopping Cart scenario.
Part 3: Hibernate for Database Updates, and the Shopping Cart Scenario
This is the third in a three-article series concerned with the configuration of Tomcat 5.5.x and Hibernate 3.1 so that these component versions can be used together. At the present time, much of the information available on the Web is unfortunately focused on earlier versions of these components, when their configuration and programming were somewhat different than at present.
In addition, the use of the Hibernate session object has changed somewhat in a manner that definitely affects its use with Tomcat (as we will show shortly), and it is important to be aware of how it works now.
A Brief Synopsis
In the previous article, the example servlet used Hibernate, not raw JDBC, to access the MySQL database, but the same Tomcat Context.xml file continued to be used, without change. Hence Hibernate took advantage of the same JNDI DataSource as the raw JDBC utilized in the first article. This second article, like the first, performed a database query (retrieval) rather than an update, and illustrated the object-oriented nature of a Hibernate database retrieval.
In this third article we have two objectives: First, we want to illustrate a Hibernate database update (actually an insert) which reflects the one-to-many Driver to Car relationship. This update will take place in a properly configured Hibernate transaction. We will illustrate both a commit and a rollback. Second, we will talk about application design in a shopping cart scenario.
Example Servlet that Performs Database Insert
In the earlier articles we looked at Context.xml file, web.xml, HibernateUtil.java, hibernate.cfg.xml, and the mapping files Driver.hbm.xml and Car.hbm.xml and their corresponding Java class files, Driver.java and Car.java. All of these files will continue to be used, without change.
Hence we can immediately focus our attention on the servlet itself, InsertViaHibernate.java. The code follows:
package tomcatJndi; import java.io.IOException; import java.io.PrintWriter; import org.apache.log4j.Logger; import org.hibernate.SessionFactory; import org.hibernate.Session; import org.hibernate.Query; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.hibernate.Session; public class InsertViaHibernate extends HttpServlet { public InsertViaHibernate() { super(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); Session session = null; try { session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); Car bigSUV = new Car(); bigSUV.setManufacturer("Ford"); bigSUV.setModel("Expedition"); bigSUV.setYear(2005); Driver jill = getDriverByName("Jill", session); addCarToDriver(jill, bigSUV, session); session.getTransaction().commit(); //Automatically closes session } catch (Exception e) { session.getTransaction().rollback(); throw new ServletException(); } out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } public Driver getDriverByName (String name, Session session) { Query q = session.createQuery("from Driver d where d.name = :name"); q.setString("name," name); Driver newOwner = (Driver) q.list().get(0); return newOwner; } public void addCarToDriver(Driver theOwner, Car theCar, Session session) { theOwner.getCarsOwned().add(theCar); session.save(theCar); session.save(theOwner); } }
The first thing to notice in this program is that - like in the second article - we begin by referencing the provided Hibernate session via the method call SessionFactory.openSession (with the help of HibernateUtil), then we start a Hibernate transaction with: Session.beginTransaction(). The method call to addCarToDriver() causes both object instances - the Car and the Driver - to be saved in the Hibernate Session. No actual database update has yet occurred. When we return to the doGet() method, and it invokes session.getTransaction().commit(), both MySQL tables are actually updated.
In the previous article, we noted the object-oriented approach followed by the Hibernate query. Here you see it again in the updates - only the Java classes are referenced, not the MySQL tables. Nowhere in the Java code is there a reference to the database column car.fk_driver_id, the foreign key in the car table which allows this table and the driver table to be joined together when using raw JDBC.
Note the rollback statement in the catch clause above. This needs to be tested. One way to test it is to change the statement:
Driver jill = getDriverByName("Jill");
to:
Driver nosuch = getDriverByName("Melvin");
Examine the method getDriverByName(). As written, it assumes the q.list() method will return a java.util.List having (at least) one member. However, this family has no member named "Melvin." As a result, q.list().get(0) will throw an IndexOutOfBoundsException, which is returned to the catch clause in doGet(). In turn, doGet() will invoke getTransaction.rollback().
The Multi-Http-Request Business Transaction
In all the examples thus far in this article series, the method calls
session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction();
and
session.getTransaction().commit(); //Automatically closes session
occur in the same Http request. There is no series of screens separating the beginTransaction from the commit, and no intervening user "think time." That is as it should be, because session.beginTransaction opens an actual database transaction, and session.getTransaction.commit actually performs the database commit. These steps should never be separated by any user "think time." A user could conceivably be looking at a given screen, in the middle of a series of screens which in the aggregate comprise a single business transaction, for five minutes or more. To hold the database lock open for such a long time could certainly bottleneck many concurrent transactions from other active users.
The Hibernate reference documentation and other published Hibernate materials refer to this kind of multi-Http-request business transactions as a "long running transaction," or as a "conversational transaction." (This scenario is also referred to as a â'œbusiness transactionâ''. Note all three phrases are synonymous, and none of them has anything to do with a database transaction or a Hibernate transaction.) Hibernate provides tools for dealing with long-running transactions. For example, you can detach objects from the Hibernate session before the intervening user "think time," then reattach them when the user submits the next screen and you are ready to work with them again. We have seen recommendations in the Hibernate literature that the scope of the open Hibernate Session be kept short - as limited as possible - as a first choice. We would rather make this recommendation in the strongest possible terms. By all means, open the Hibernate Session and begin the transaction, save whatever object instances you need to save, then commit the transaction, within a single method - as in the examples you have seen to this point in this article series. Be strongly inclined to avoid any other approach. Do not attempt to make use of a Hibernate session which remains open across multiple Http requests.
But what about the multi-Http-request business transaction, you may very reasonably ask? For instance, what about a shopping cart scenario? Let's see.
The Shopping Cart Scenario
How would you design this type of application if there were no such thing as Hibernate, or in a non-Hibernate environment? The approach people have used for years is to use staging tables (or temp tables, or work tables) within the database. Hence as the user marches through the series of screens, with each postback to the server, a new Hibernate Session would be opened, session.beginTransaction would be performed, one or more saves or similar methods would be invoked, then the transaction commit would be performed. Only at the very end of the series of screens - at the end of the business transaction or conversation - would data be transferred from the staging tables to the permanent tables, and the same data be deleted from the staging tables.
This approach, we think, makes as much sense in a Hibernate environment as in a non-Hibernate environment, and keeps everything simple and predictable. And it is quite portable to any database platform.
As a general rule, there can be some small difference between the structure of the permanent table and the structure of the corresponding staging table. The staging table will contain a column such as the Http Session ID to identify the user session. Normally this column would not be present in the permanent table. In your servlet, you can obtain the Http Session ID as follows:
request.getSession().getId()
The primary key in the two tables, assuming it is a surrogate key assigned by the database or by Hibernate, may perhaps have the same datatype or structure, but will not have the same value. The primary key value in the staging table is nothing but a temporary convenience.
There may be some other small differences between the two record structures, resulting from the fact that the permanent record has been fully processed.
Let's pretend that the car table in the MySQL database needs to go through some kind of multi-Http-request scenario, and needs a staging table. Here is a possible "create table" SQL statement:
create table car_staging( car_id bigint(20) PRIMARY KEY not null auto_increment, http_session_id varchar(64), manufacturer varchar(40), model varchar(40), year int, fk_driver_id bigint(20), FOREIGN KEY (fk_driver_id) REFERENCES driver (driver_id) )
The corresponding "create table" SQL statement for the permanent car table was presented in the previous article. The only difference between the two SQL statements in this example is the name of the table and the addition of the column http_session_id to the staging table record layout.
Here's the corresponding Java source file, Car_staging.java. It is nearly identical to the module Car.java that was presented in the previous article.
package tomcatJndi; public class Car_staging { private Long car_id; private String http_session_id; private String manufacturer; private String model; private int year; public Car() {} public Long getId() { return car_id; } private void setId(Long car_id) { //Note private visibility this.car_id = car_id; } public String getHttp_session_id() { return http_session_id; } public void setHttp_session_id(String http_session_id) { this.http_session_id = http_session_id; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } }
And here is the corresponding Hibernate mapping file. Again, it is nearly identical to the mapping file for the permanent car table, shown in the previous article.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="tomcatJndi.Car_staging" table="CAR_STAGING"> <id name="id" column="CAR_ID" type=â''longâ''> <generator class="native"/> </id> <property name="http_session_id"/> <property name="manufacturer"/> <property name="model"/> <property name="year" type="integer"/> </class> </hibernate-mapping>
Of course, you don't have to use staging tables. The alternative exists of detaching and attaching, of deciding just how to employ optimistic locking with Hibernate, and dealing with related issues. But why would this be helpful or desirable? What does it simplify?
Say you wanted to ask the Hibernate Session for all the object instances that had been saved in it during the course of the several interchanges with the user. Presumably you would want an Iterator or an Enumeration. How would you get it? We don't know either. Hence if you may need to do certain late-in-the-process updates or interrogations of the data saved in the Hibernate Session, you will need to save independent references to such data outside of the Hibernate Session, in addition to your having saved this information inside the Hibernate Session.
If you need to store the updated information outside the Hibernate Session, what's the point in also storing it inside the Hibernate Session - from where you cannot even iterate through it? We think the best approach is to store information inside the Hibernate Session when you're ready to commit it.
There are other techniques in widespread use in addition to the staging table technique. For instance, you can store the information from the long-running transaction in the Http session (as opposed to the Hibernate session). However, if a substantial amount of data is stored in the Http session in this way, scalability of your application can be compromised. In addition, server farm clustering can become problematic.
You may also elect to serialize the information in some manner and download it in an HTML Hidden (<input type=â''hiddenâ''>) on the client-side. However, this approach can cause a significant amount of data to be transmitted back-and-forth between the server and client, and you will probably need to do something to make sure the serialized information in the browser is secure. In any web application, the three traditional ways to support the shopping cart scenario are as described above: (1) staging tables (2) attributes in the Http session (3) serialization within HTML Hiddens (probably the least common approach). In non-Hibernate environments, using the most current web MVC technologies - JSF, or Spring MVC, or Struts - you would surely use one of the three approaches described above. We think the same three approaches should be considered in a Hibernate-based web application - make your choice from among them based on the amount of information to be stored in the active shopping cart, and on the other considerations listed above.