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
ersistence 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]+{c} to 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(3) IDENTITY,
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, symbol) values ('Manganese','25','55','Mn');
insert into elements ( name, number, mass, symbol) values ('Zinc','30','65','Zn');
insert into elements ( name, number, mass, symbol) values ('Thulium','69','169','Tm');
insert into elements ( name, number, mass, symbol) values ('Californium','98','251','Cf');
insert into elements ( name, number, mass, symbol) values ('Gold','79','197','Au');
insert into elements ( name, number, mass, symbol) values ('Ytterbium','70','173','Yb');
insert into elements ( name, number, mass, symbol) values ('Molybdenum','42','96','Mo');
insert into elements ( name, number, mass, symbol) values ('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 = (SearchForm) form;
Element element = null;
List elements = null;
SessionFactory factory = null;
Session session = null;
try {
factory =
(SessionFactory) servlet.getServletContext()
.getAttribute(HibernatePlugin.KEY_NAME);
session = factory.openSession();
elements =
session.find(
HQL_FIND_ELEMENT,
searchForm.getSymbol(),
Hibernate.STRING);
if (!elements.isEmpty()) {
element = (Element) elements.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.
Danilo Gurovich is a manager of Web engineering at an e-commerce company. He has designed e-commerce and ERP/EAI Struts applications, and has led teams who have built them.
posted on 2005-09-18 14:11
R.Zeus 阅读(751)
评论(0) 编辑 收藏 所属分类:
STRUTS 、
Hibernate