http://www.javaworld.com/javaworld/jw-01-2005/jw-0124-strutshibernate.html

Struts Recipes: Hibernate and Struts#

Add the power of Hibernate to your Struts application

Summary

In this excerpt from Struts Recipes, (Manning Publications, December 2004) authors George Franciscus and Danilo Gurovich illustrate how to use Hibernate in a Struts application. They also show how to create a Struts plug-in to improve performance. (2,200 words; January 24, 2005)

By George Franciscus and Danilo Gurovich

Persistence is a fundamental piece of an application. Obviously, without persistence all work would be lost. However, persistence means different things to different people. The length of time something must be persisted is a fundamental qualifier in choosing a persistence storage medium. For example, the HTTP session may be suitable when the life of a piece of data is limited to the user's session. In contrast, persistence over several sessions, or several users, requires a database. The volume of data is another important qualifier. For example, best practices suggest large amounts of data should not be stored in an HTTP session. In those circumstances, you need to consider a database. In this recipe we target persistence in a database.

The type of database you choose has an important influence on your architecture and design. As object-oriented developers, we tend to represent data as an interconnected web of objects as a means to describe the business problem at hand—this is often called a domain model. However, the most common storage medium is based on a relational paradigm. Unless our object model mirrors a relational structure, the in-memory representation of our data is at odds with the means to persist it. This problem is called the mismatch paradigm. One of the most popular tools to address the mismatch problem is a category of tools called object-relational mappers. An object-relational mapper is software used to transform an object view of the data into a relational one, and provide persistence services, such as create, read, update, and delete (CRUD). Many good papers have been written on object-relational mappers, but in essence, they all speak to the Data Mapper pattern. One of the most popular object-relational mappers is the open source Hibernate project.

In this recipe, we show you how to employ Hibernate in a Struts application. In addition, we will show you how to create a Struts plug-in to give your Hibernate-powered Struts applications a performance boost.

Recipe

In this recipe, we use an example to illustrate everything you need to do to use Hibernate in a Struts application. We create an application to retrieve and display elements from the chemical periodic table. The application offers the user a search page to look for an element by element symbol. The application responds by searching the database for an element matching the symbol name and returns information about the element.

We'll start by showing you how to get the Hypersonic database server up and running. With the database server started, we create the table and data required to exercise the application. Once the database is ready to go, we'll create all the Hibernate artifacts required to execute this application by using the Hypersonic database server. The next step is to respond to search requests by calling upon Hibernate to handle database access from inside our Action. Because creating Hibernate factory objects is expensive, we'll create a Struts plug-in to create the factory and store it in context.

Let's start by bringing up the Hypersonic database server. You need to download Hypersonic from http://hsqldb.sourceforge.net/. Place hsqldb.jar in your classpath and launch Hypersonic by entering the following command in your DOS prompt:

java org.hsqldb.Server 

Although the server's response varies from one version of Hypersonic to another, the following response is a typical indication that Hypersonic is ready to serve database requests.

Server 1.6 is running
Press [Ctrl]+{cto abort 

With the database server up and running, we are ready to create the elements table and populate it with data, as shown in Listing 1.

Listing 1. Create and populate elements tables

create table elements (id integer(3IDENTITY,
                 name char(30),
                 number char(30),
                 mass char(30),
                 symbol char(2));

CREATE UNIQUE INDEX ui_elements_pk ON elements (symbol)

insert into elements name, number, mass, symbolvalues ('Manganese','25','55','Mn');
insert into elements name, number, mass, symbolvalues ('Zinc','30','65','Zn');
insert into elements name, number, mass, symbolvalues ('Thulium','69','169','Tm');
insert into elements name, number, mass, symbolvalues ('Californium','98','251','Cf');
insert into elements name, number, mass, symbolvalues ('Gold','79','197','Au');
insert into elements name, number, mass, symbolvalues ('Ytterbium','70','173','Yb');
insert into elements name, number, mass, symbolvalues ('Molybdenum','42','96','Mo');
insert into elements name, number, mass, symbolvalues ('Palladium','46','106','Pd'); 

Listing 1 presents the SQL commands necessary to create the elements table, create a unique index on symbol, and insert data We have only presented a few of the periodic elements. We'll leave it to you to dust off your high school chemistry textbook to create data for the remaining elements.

Listing 2 presents the Element JavaBean used to store data retrieved from the database.

Listing 2. Element JavaBean

package com.strutsrecipes.hibernate.beans;

public class Element {
   private String name;
   private String symbol;
   private String number;
   private String mass;
   private int id;

   public Element() {
      super();
   }

   public Element(String name, String symbol, String number, String mass) {
      this.name = name;
      this.symbol = symbol;
      this.number = number;
      this.mass = mass;

   }

   public int getId() {
      return id;
   }

   public void setId(int id) {
      this.id = id;
   }

   public String getMass() {
      return mass;
   }

   public String getName() {
      return name;
   }

   public String getNumber() {
      return number;
   }

   public String getSymbol() {
      return symbol;
   }

   public void setMass(String mass) {
      this.mass = mass;

   }

   public void setName(String name) {
      this.name = name;
   }

   public void setNumber(String number) {
      this.number = number;
   }

   public void setSymbol(String symbol) {
      this.symbol = symbol;
   }

Hibernate is an object-relational mapping tool. Its job is to map objects to relational tables and vice versa. Therefore, we must tell Hibernate how to map the columns in the "elements" table to the properties of the Elements JavaBean. This is done using the Element.hbm.xml file. The information embodied in this file is required to empower Hibernate to copy data from the table to the Elements JavaBean. If we were using Hibernate to update data, the information in the Element.hbm.xml file would be used to extract data from the Elements JavaBean to generate SQL update statements. Listing 3 presents Element.hbm.xml.

Listing 3. Element.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sf.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
   <class name="com.strutsrecipes.hibernate.beans.Element" table="elements">

      <id name="id" column="id">
         <generator class="native"/>
      </id>

      <property name="name" column="name"/>
      <property name="number" column="number"/>
      <property name="mass" column="mass"/>
      <property name="symbol" column="symbol"/>
   </class>
</hibernate-mapping> 

Let's step through Listing 3

We declare the full package name of the class to be associated with the "elements" table. We then declare the name of the table associated with that class. Next, we declare the mapping from the id JavaBean property to the id column. Because the property and column names have the same value, we could have omitted the column attribute, but we have explicitly declared the column for clarity purposes. The <id> tag is a special tag. It is used to declare the primary key for the table. The enclosing <generator> tag instructs Hibernate to generate the key in whichever way is most appropriate for the database implementation. You should consult Hibernate documentation for more information on the <id> tag. Finally, we declare mapping for the remaining JavaBean properties. Once again the column attribute is declared for clarification purposes.

Once the mapping file has been broken down in detail, it's all rather straightforward. It simply describes which table maps to which class and which JavaBean properties map to which column names. Later on we will tell you where to place this file.

Next, we configure Hibernate by declaring environmental information. In Listing 4, we present the hibernate.cfg.xml file.

Listing 4. hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>
   <session-factory>
   <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
   <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
   <property name="connection.username">sa</property>
   <property name="connection.password"></property>
   <property name="connection.url">jdbc:hsqldb:hsql://127.0.0.1</property>
   <property name="show_sql"> </property>
   <property name="">true</property>

   <mapping resource="/com/strutscookbook/hibernate/beans/Element.hbm.xml"/>
</session-factory>
</hibernate-configuration> 

Let's step through Listing 4.

We start by specifying the database implementation dialect that allows Hibernate to take advantage of implementation-specific features. We declare the Hypersonic dialect. You should consult the Hibernate documentation to choose the appropriate dialect for your database. We then declare the database driver. You must ensure this driver is in your application's classpath. We then declare the database username, the database password, and the database connection URL. Next, we instruct Hibernate to display the SQL generated at runtime in the log.

The hibernate.cfg.xml file must be placed in your classpath.

The procedure to use Hibernate within your application requires the following steps:

* Create a Hibernate configuration object

* Use the Hibernate configuration object to create a Hibernate factory object

* Use the Hibernate factory object to create a Hibernate session object

* Use the Hibernate session object to start a transaction (optional)

* Employ the Hibernate session object to create, read, update, and delete data on the database

* Commit the transaction (optional)

Close the session A Hibernate best practice is to create and cache the Hibernate factory to enhance performance. Therefore, we will create a Struts plug-in to perform Steps 1 and 2 and cache the Hibernate factory in the servlet context, as shown in Listing 5.

Listing 5. HibernatePlugin.java

package com.strutsrecipes.hibernate.plugin;

import java.net.URL;
import javax.servlet.ServletException;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.MappingException;
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.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;

public class HibernatePlugin implements PlugIn {
   private Configuration config;
   private SessionFactory factory;
   private String path = "/hibernate.cfg.xml";
   private static Class clazz = HibernatePlugin.class;

      public static final String KEY_NAME = clazz.getName();

      private static Log log = LogFactory.getLog(clazz);

   public void setPath(String path) {
      this.path = path;
   }

   public void init(ActionServlet servlet, ModuleConfig modConfig)
      throws ServletException {

      try {
         URL url = HibernatePlugin.class.getResource(path);
         config = new Configuration().configure(url);
         factory = config.buildSessionFactory();
         servlet.getServletContext().setAttribute(KEY_NAME, factory);

      catch (MappingException e) {
         log.error("mapping error", e);
         throw new ServletException();

      catch (HibernateException e) {
         log.error("hibernate error", e);
         throw new ServletException();
      }

   }

   public void destroy() {
      try {
         factory.close();
      catch (HibernateException e) {
         log.error("unable to close factory", e);
      }
   }

Creating a Struts plug-in requires only two steps. First, create a class implementing org.apache.struts.action.PlugIn (Listing 5). Second, define a <plug-in> tag in the struts-config.xml file (Listing 6).

Let's step through Listing 5.

We create a constant to hold the name of the servlet context attribute key. We have chosen to use the HibernatePlugin class name. Notice the constant is static public final. We use the HibernatePlugin class to access the key name in the Action (Listing 7). We define the path property. By default, the Hibernate-Plugin looks for the Hibernate configuration file at /hibernate.cfg.xml. You can use this property to load the Hibernate configuration file from another filename and directory anywhere on the classpath. Next, we use the classloader to find the Hibernate configuration file and then we create the Hibernate configuration object. We use the Hibernate configuration object to create a Hibernate factory object and then we store the Hibernate factory object in the servlet context. The factory is now available to any code with a reference to the servlet.

As a good practice, we close the factory in the destroy method.

Listing 6 presents the application struts-config. The only thing out of the ordinary here is the <plug-in> tag. This is where we declare the Hibernate plug-in, which creates and caches the Hibernate factory object.

Listing 6. struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
   "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
   <form-beans>
      <form-bean name="searchForm"type="com.strutsrecipes.hibernate.forms.SearchForm"/>
   </form-beans>

   <global-forwards>
      <forward name="search" path="/search.jsp"/>
      <forward name="searchsubmit" path="/searchsubmit.do"/>
   </global-forwards>

   <action-mappings>
      <action path="/searchsubmit"
            type="com.strutsrecipes.hibernate.actions.SearchAction"
            name="searchForm"
            scope="request"
            input="/search.jsp">
            <forward name="success" path="/element.jsp"/>
      </action>
   </action-mappings>

   <plug-in className="com.strutsrecipes.hibernate.plugin.HibernatePlugin">
      <set-property property="path" value="/hibernate.cfg.xml"/>
   </plug-in>
</struts-config> 

Listing 7 presents the SearchForm used to search for an element. It's very simple because the user can only search by element symbol.

Listing 7. SearchForm.java

package com.strutsrecipes.hibernate.forms;

import org.apache.struts.action.ActionForm;
public class SearchForm extends ActionForm {
   String symbol;

   public String getSymbol() {
      return symbol;
   }

   public void setSymbol(String symbol) {
      this.symbol = symbol;
   }

Let's have a look at the SearchAction in Listing 8. Although you may decide to employ Hibernate in other areas of your application architecture, we have chosen to use it in the Action. We'll defer the discussion of the other alternatives to the discussion section.

Listing 8. SearchAction.java

package com.strutsrecipes.hibernate.actions;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import com.strutsrecipes.hibernate.beans.Element;
import com.strutsrecipes.hibernate.forms.SearchForm;
import com.strutsrecipes.hibernate.plugin.HibernatePlugin;

public class SearchAction extends Action {
   private static Log log = LogFactory.getLog(SearchAction.class);

   final public static String HQL_FIND_ELEMENT =
      "from com.strutsrecipes.hibernate.beans.Element as e where e.symbol = ?";

   public ActionForward execute(
      ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response)
      throws Exception {

      SearchForm searchForm = (SearchFormform;
      Element element = null;
      List elements = null;
      SessionFactory factory = null;
      Session session = null;

      try {

         factory =
                (SessionFactoryservlet.getServletContext()
               .getAttribute(HibernatePlugin.KEY_NAME);

         session = factory.openSession();

         elements =
            session.find(
            HQL_FIND_ELEMENT,
            searchForm.getSymbol(),
            Hibernate.STRING);

         if (!elements.isEmpty()) {
            element = (Elementelements.get(0);
         }

      catch (HibernateException e) {
         log.error("Hibernate error", e);
         finally {
            log.error("Hibernate exception encountered");
         session.close();
      }

      if (element != null) {
            request.setAttribute("element", element);
            return mapping.findForward("success");
      }

      ActionErrors errors = new ActionErrors();

      errors.add(ActionErrors.GLOBAL_ERROR,
         new ActionError("error.notfound"));
      saveErrors(request, errors);
      return mapping.getInputForward();
   }

Let's take a quick overview of what happens in the SearchAction. The SearchAction uses the SearchForm.getSymbol() method to obtain the element symbol entered by the user on the search page. Hibernate is used to search the database and convert the data stored in the database to an Element object. The Element object is placed in request context for the JavaServer Pages (JSP) page. Let's step through Listing 8 line by line to see how it's done in detail.

First, we declare a constant to search the database. We next cast the form to SearchForm and then we obtain the Hibernate factory. Recall the Hibernate plug-in has already created the factory and cached it in the servlet context. Next, we obtain a session. The session obtains a connection to the database. Hibernate uses the configuration information we created in Listing 4 to connect to the database. We then search the database.

There are other ways to employ Hibernate to search the database, but the find method is appropriate whenever a search doesn't use the primary key. Notice, we have the HQL_FIND_ELEMENT constant declared. The SQL defined in HQL_FIND_ELEMENT looks somewhat like standard SQL, but not quite. The SQL used by Hibernate is proprietary to Hibernate and reflects an object-oriented version of SQL, rather than the relational SQL to which you are accustomed.

Let's delve into the Hibernate SQL (HQL) code snippet.

from com.strutsrecipes.hibernate.beans.Element as e where e.symbol = ?

This statement tells Hibernate to select all Element objects residing in the com.strutsrecipes.hibernate.beans package. The where clause filters the list to only those elements whose symbols match a runtime parameter. The as e indicates that e may be used as an alias elsewhere in the HQL, as we have done in the where clause. You can see that we are selecting objects, not rows, in the database. Hibernate uses the information in Listing 4 to map the class we are interested in to its associated table. In this example, the relationship between the table and the object are very close, but that does not necessarily need to be the case.

The second and third arguments to the find method are the value and data type of the HQL replacement parameter. The Hibernate reference material describes other ways to replace runtime parameters.

The find method always returns a List. In this case, we obtain a list of Element objects. We are confident that a maximum of one instance is returned because the "elements" table has a unique key constraint on the symbol column (see Listing 1).

Returning to Listing 8, we copy the element reference in the first position in the list to the element variable. To deal with any Hibernate exceptions, we have chosen to log the exception and present the user a "not found" message, but you may decide to present a different message or use declarative exception handling. Next, we close the session. Closing the session in the finally clause guarantees it is attempted even when exceptions are thrown. We store the Element object in the request context and finally we build the ActionError when the symbol can't be found. For the sake of completeness, we have presented the search.jsp (Listing 9) and the element.jsp (Listing 10).

Listing 9. Search.jsp

<%page language="java" %>
<%taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html:html>
<body>
   <h1>Search for an Element</h1>

   <html:form action="/searchsubmit.do">
      symbol <form:text property="symbol"/>
   <html:submit value="Search"/>
   </html:form>

   <html:errors/>

</body>
</html:html> 

Listing 10. Element.jsp

<%page language="java" %>
<%taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html:html>
   <h1>Periodic Element</h1>
   Name: <bean:write name="element" property="name"/><br>
   Symbol: <bean:write name="element" property="symbol"/><br>
   Number: <bean:write name="element" property="number"/><br>
   Mass: <bean:write name="element" property="mass"/><br>

   <html:link forward="search">Search</html:link><p>
</html:html> 

Before putting Hibernate to work, consult the Hibernate documentation to ensure you have all the required Hibernate jar files in your classpath.

Discussion Persistence of data is a tedious and laborious job. To make matters worse, a considerable effort must be spent transforming an object-oriented representation of the data to a relational one, and vice versa. Fortunately, several good object-relational mappers exist to ease this burden. In this recipe, we explored Hibernate—one of the most popular open source object-relational mappers available to Java programmers.

Hibernate is a very rich product with many unexplored features left for you to discover. Our simple example is limited to read behavior, but the rest of the CRUD family is just as easy. Update functionality is as simple as accessing the desired element, calling the desired JavaBean setter, and calling the session commit method. Hibernate takes care of generating the SQL and updating the table. A delete is also rather simple—session.delete(element) is all it takes! Finally, create only requires instantiating the object, calling the setters, and calling session.save(element).

Hibernate best practices recommend caching the Hibernate factory object. We chose to create and cache the factory using a Struts plug-in. Alternatively, you could have chosen to cache using any other means in your arsenal.

Although this recipe can serve you well, there are some drawbacks. First, we have exposed Hibernate to the Struts Action. Migrating to another persistence layer framework requires us to change every Action employing Hibernate. Second, our persistence is tightly coupled to the presentation layer. This coupling denies us the opportunity to reuse the persistence logic in some other presentation mechanism, such as a batch program.

Although there is room for improvement, this recipe is suitable when you do not expect to reuse your persistence logic. You may find yourself in this situation developing prototypes or small throwaway applications.

About the author George Franciscus is a J2EE consultant and Struts authority. He is a coauthor of Manning's Struts in Action


Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-6) was last changed on 28-Nov-2007 09:57 by DongGukLee