Introduction
In my previous post, How to integrate Spring Framework with a Struts2 application, I created a simple web application that used Struts2 and Spring. In this post, I am going to extend that application to add persistence logic using JPA (Java Persistence Architecture) and Hibernate.
I am assuming that you are already familiar with the technologies used in this post – Struts2, Spring and Hibernate.
My goal is to create a project that can be used to jumpstart a Struts2, Spring and Hibernate based development. This is not a detailed tutorial for these technologies.
My current development environment (at the time of developing this project) looks like this:
- Maven: 2.2.1
- Java: 1.6.0_20
- Mac OS X 10.6.4
- Struts: 2.1.8
- Spring: 2.5.6
- Hibernate: 3.4.0.GA
Configure pom.xml
Before we begin, we need to add JPA and Hibernate dependencies to the pom.xml
.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.rajandesai.example</groupId> <artifactId>webapp</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>war</packaging> <name>webapp</name> <url>http://maven.apache.org</url> <!-- Shared version number properties --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <struts2.version>2.1.8</struts2.version> <hibernate.em.version>3.4.0.GA</hibernate.em.version> <spring.version>2.5.6</spring.version> <spring.jpa.version>2.0.8</spring.jpa.version> <mysql.connector.version>5.1.13</mysql.connector.version> <junit.version>4.4</junit.version> <log4j.version>1.2.16</log4j.version> </properties> <dependencies> <!-- Struts 2 --> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> <version>${struts2.version}</version> </dependency> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-spring-plugin</artifactId> <version>${struts2.version}</version> </dependency> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-junit-plugin</artifactId> <version>${struts2.version}</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jpa</artifactId> <version>${spring.jpa.version}</version> </dependency> <!-- MySQL JDBC Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.connector.version}</version> </dependency> <!-- Servlet & Jsp --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <!-- Hibernate dependencies --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.em.version}</version> </dependency> <!-- Other dependencies --> <!-- slf4j required for hibernate-annotations 3.4.0 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.6</version> </dependency> <!-- concrete Log4J Implementation for SLF4J API--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1-beta-1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.0.0</version> <configuration> <contextPath>/</contextPath> <scanIntervalSeconds>3</scanIntervalSeconds> <scanTargets> <scanTarget> src/main/webapp/WEB-INF/web.xml </scanTarget> </scanTargets> </configuration> </plugin> </plugins> <finalName>webapp</finalName> </build> </project>
By adding hibernate-entitymanager
to the pom,all the other required dependencies (JTA, Hibernate annotations et al.) are resolved automatically.
Create database schema
Let’s create the user
table to store the user data.
CREATE TABLE IF NOT EXISTS `examples`.`user` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL , `zip` VARCHAR(10) NULL , `age` INT NULL , `gender` ENUM('M', 'F') NOT NULL DEFAULT 'M' , PRIMARY KEY (`id`) ) ENGINE = InnoDB;
Note: I am using a schema examples
created in my local MySQL instance. Remember to replace values for schema name, user name and password with the correct values in your environment.
Define the domain model
We will modify the User
model created in the previous Struts application. Let’s add the required annotations to this model so that it can be persisted to the database.
package com.rajandesai.example.webapp.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; /** * Defines a model that represents a user in the system. */ @Entity @Table(name = "user") public class User implements Serializable { private static final long serialVersionUID = -2230673925164485629L; @Id @GeneratedValue private Long id; private String name; private String zip; private int age; private char gender; /** * Sets the unique id for this user * @param id unique id to be assigned */ public void setId(Long id) { this.id = id; } /** * Gets the unique id assigned to this user * @return the unique id */ public Long getId() { return id; } /** * Gets the name of this user * @return the name */ public final String getName() { return name; } /** * Sets the name of this user. * @param name the name to set */ public final void setName(String name) { this.name = name; } /** * Gets the zip code of this user * @return the zip code of the user */ public final String getZip() { return zip; } /** * Sets the zip code of this user * @param zip the zip code to set */ public final void setZip(String zip) { this.zip = zip; } /** * Gets the age of this user * @return the age of this user */ public final int getAge() { return age; } /** * Sets the age of this user * @param age the age to set */ public final void setAge(int age) { this.age = age; } /** * Gets the gender of this user * @return the gender of this user */ public final char getGender() { return gender; } /** * Sets the gender of this user * @param gender the gender to set */ public final void setGender(char gender) { this.gender = gender; } }
@Entity
lets the provider (Hibernate in our case) know that this model can be persisted. By default, classes and fields are mapped to the table and columns with the same name. @Table
and @Column
annotations can be used to customize the default mapping for classes and fields. @Id
annotation marks the field as the primary key for the class. In JPA an @Id
can be easily assigned a generated sequence number through the @GeneratedValue
annotation.
Note: We added a new field id
that represents the unique, generated id of the user. This field maps to the primary key in the database.
Define the DAOs
We will define a generic DAO (data access object) that defines the basic CRUD operations for our domain model.
Use os java generics allows us to define a generic interface that is typesafe (no type casting required).
Note: I copied this implementation idea from Matt Riable’s AppFuse project. Thanks to him and others, who are doing a wonderful job of maintaining AppFuse project. I still use AppFuse when I need more than a simple Twisted Hello World example.
package com.rajandesai.example.webapp.dao; import java.io.Serializable; import java.util.List; /** * Defines a generic data access object that is extended by all the DAOs in the * system. This interface defines the common CRUD methods like <code>save</code>, * <code>delete</code> <code>getAll</code> and <code>getById</code>. */ public interface GenericDao <T, ID extends Serializable> { /** * Gets the list of all objects of this type * @return list of this objects; the list can be empty */ public List<T> getAll(); /** * Gets the object using specified primary key * @param id primary key (ID) of the object to retrieve * @return object corresponding to the specified primary key (ID) * @throws org.springframework.orm.ObjectRetrievalFailureException when object * corresponding to the specified ID (primary key) is not found. This is a * <code>RuntimeException</code> */ public T getById(ID id); /** * Saves the specified object. Handles both insert as well as update * @param object the object to save * @return managed copy of the original object. Use this object if you want * the future changes managed (persisted). */ public T save(T object); /** * Deletes the object that corresponds to the specified primary key (ID) * @param object the object to delete */ public void delete(ID id); }
Next, let’s implement a base class that uses JPA for persistence. This abstract class holds reference to the EntityManager
for all the derived DAO classes and provides a generic, typesafe implementation of GenericDao
interface using java generics.
package com.rajandesai.example.webapp.dao.jpa; import java.io.Serializable; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.rajandesai.example.webapp.dao.GenericDao; /** * Defines a Base DAO that provides JPA implementation. * This class keeps the reference to the <code>EntityManager</code> and * provides a default implementation for methods defined in the <code>GenericDao</code> * interface. */ public abstract class BaseJpaDao<T, ID extends Serializable> implements GenericDao<T, ID> { private Class<T> persistentClass; protected EntityManager entityManager; /** * Constructor that takes in a class to see which type of entity to persist * @param persistentClass the class type you'd like to persist */ public BaseJpaDao(final Class<T> persistentClass) { this.persistentClass = persistentClass; } /** * Gets the <code>EntityManager</code> used by this DAO * @return the entityManager used for persistence */ public final EntityManager getEntityManager() { return entityManager; } /** * Sets the <code>EntityManager</code> used by this DAO * @param entityManager the entityManager to be used for persistence */ @PersistenceContext public final void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } @Override public void delete(ID id) { this.entityManager.remove(id); } @SuppressWarnings("unchecked") @Override public List<T> getAll() { Query q = this.entityManager.createQuery("FROM " + this.persistentClass.getName()); return q.getResultList(); } @Override public T getById(ID id) { final T data= this.entityManager.find(persistentClass, id); return data; } @Override public T save(T object) { try { final T data = this.entityManager.merge(object); return data; } catch (Exception e) { e.printStackTrace(); } return null; } }
@persitenceContext
is used to tell the Spring framework to inject EntityManager
in this base DAO implementation. I have placed it on the setter method, but it can also be placed on the field.
UserDao
defines additional finder method for the User
entity.
package com.rajandesai.example.webapp.dao.jpa; import com.rajandesai.example.webapp.model.User; /** * This interface defines additional data access methods for the <code>User</code> * entity. */ public interface UserDao extends GenericDao<User, Long> { /** * Gets the user using specified name * @param name name of the user to be searched * @return <code>User</code> object retrieved using specified user name * @throws org.springframework.orm.ObjectRetrievalFailureException when object * corresponding to the specified ID (primary key) is not found. This is a * <code>RuntimeException</code> */ public User getByName(String name); }
The actual implementation of the UserDao
looks like this.
package com.rajandesai.example.webapp.dao.jpa.impl; import javax.persistence.Query; import com.rajandesai.example.webapp.dao.jpa.BaseJpaDao; import com.rajandesai.example.webapp.dao.jpa.UserDao; import com.rajandesai.example.webapp.model.User; public final class UserDaoImpl extends BaseJpaDao<User, Long> implements UserDao{ public UserDaoImpl() { super(User.class); } @Override public User getByName(String name) { Query q = this.entityManager.createQuery("SELECT u FROM User u where u.name = :name"); q.setParameter("name", name); User u = (User)q.getSingleResult(); return u; } }
Define the Service Layer
Define an interface that defines the basic operations for the User
entity.
package com.rajandesai.example.webapp.service; import java.util.List; import org.springframework.transaction.annotation.Transactional; import com.rajandesai.example.webapp.model.User; public interface UserService { /** * Creates a new user * @param user new user to be created * @return updated user object that should be used for further updates * @throws javax.persistence.PersistenceException when the user can not be * persisted. This is a <code>RuntimeException</code> */ public User createUser(User user); /** * Deletes the specified user * @param user user object to be deleted * @throws javax.persistence.PersistenceException when the user can not be * deleted. This is a <code>RuntimeException</code> */ public void deleteUser(User user); /** * Updates the specified user * @param user user object to be updated * @return updated user object that should be used for further updates * @throws javax.persistence.PersistenceException when the user can not be * persisted. This is a <code>RuntimeException</code> */ public User updateUser(User user); /** * Gets the user using specified user id * @param id unique id assigned to the user * @return <code>User</code> object; <code>null</code> if the user does not * exist in the database */ public User getUser(Long id); /** * Gets all the users * @return list of all the users */ public List<User> getAllUsers(); /** * Gets the user using specified user name * @param name user's name * @return <code>User</code> object corresponding to the specified name; * <code>null</code> if the user does not exist in the database */ public User getUserByName(String name); }
@Transactional
annotation is used at the class level so that all the methods are run inside a transaction.
It is possible to specify @Transactional
on the individual method.
The actual implementation of the UserService
interface is shown below. This implementation uses the UserDao
to actually persist the object.
package com.rajandesai.example.webapp.service.impl; import java.util.List; import javax.persistence.NoResultException; import org.apache.log4j.Logger; import com.rajandesai.example.webapp.dao.jpa.UserDao; import com.rajandesai.example.webapp.model.User; import com.rajandesai.example.webapp.service.UserService; @Transactional public class UserServiceImpl implements UserService{ Logger logger = Logger.getLogger(this.getClass()); private UserDao userDao; @Override public User createUser(User user) { return userDao.save(user); } @Override public void deleteUser(User user) { userDao.delete(user.getId()); } @Override public List<User> getAllUsers() { return userDao.getAll(); } @Override public User getUser(Long id) { User user = null; try { user = userDao.getById(id); } catch (NoResultException nsre) { if (logger.isDebugEnabled()) { logger.debug("No user found for the specified ID : [" + id + "]"); } user = null; } return user; } @Override public User updateUser(User user) { return userDao.save(user); } @Override public User getUserByName(String name) { User user = null; try { user = userDao.getByName(name); } catch (NoResultException nsre) { if (logger.isDebugEnabled()) { logger.debug("No user found for the specified Name : [" + name + "]"); } user = null; } return user; } /** * Sets the DAO used by this service for persisting <code>User</code> object * @param userDao the user DAO to be used for persistence */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } /** * Gets the <code>User</code> DAO used for persistence * @return the user DAO used for persistence */ public UserDao getUserDao() { return userDao; } }
Modify the Action to use the Service layer
We are now ready to modify the HelloAction
controller class to take advantage of the new UserService
.
Notice that we have added UserService
as a member variable to the action class. We will use Spring to inject it in the action class.
We have also modified prepare
and execute
methods to implement the new business logic. In prapare stage, we try to load the User
from the database if the user name is specified in the request. The execute
method creates a new user if the specified user name is not found in the database or updates the existing user. If no name is specified, list of existing users is displayed.
package com.rajandesai.example.webapp.action; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.interceptor.ServletRequestAware; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.Preparable; import com.rajandesai.example.webapp.model.User; import com.rajandesai.example.webapp.service.UserService; /** * */ public class HelloAction extends ActionSupport implements ModelDriven<Object>, Preparable, ServletRequestAware { private static final long serialVersionUID = -3670063011948002290L; private User user; private List<User> users; private HttpServletRequest request; private UserService userService; //During prepare call, this field is set to true for the existing user. //Execute method uses this to call appropriate service method. //Remember, in struts2 new Action is instantiated for every request. private boolean isExistingUser = false; public Object getModel() { return user; } public void prepare() throws Exception { //If the user name was specified, check if it already exists in the database String name = request.getParameter("name"); if ((name == null) || (name.trim().equals(""))) { user = new User(); isExistingUser = false; } else { user = userService.getUserByName(name); if (user == null) { isExistingUser = false; user = new User(); } else { isExistingUser = true; } } } @Override public String execute () { //Let's override this method to add our application logic. //Create new user or modify existing user using specified user details //see http://rajandesai.com/blog/2010/07/14/jpa-entitymanager-persist-vs-merge/ //if you are wondering why we use the User object returned by the update //or create method of the service. if (isExistingUser) { user = userService.updateUser(user); } else { //If username is null, get the list of users from the db. if (user.getName() != null) { user = userService.createUser(user); } else { users = userService.getAllUsers(); } } return SUCCESS; } public void setServletRequest(HttpServletRequest httpServletRequest) { request = httpServletRequest; } /** * @return the user */ public final User getUser() { return user; } /** * @param user the user to set */ public final void setUser(User user) { this.user = user; } /** * @param users the users to set */ public void setUsers(List<User> users) { this.users = users; } /** * @return the users */ public List<User> getUsers() { return users; } /** * @param userService the userService to set */ public void setUserService(UserService userService) { this.userService = userService; } /** * @return the userService */ public UserService getUserService() { return userService; } }
There is no change in the struts.xml
created in the previous post, but I modified the hello.jsp
to show the list of users when no user name is specified in the request. The new JSP looks like this:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Hello World with a Twist</title> </head> <body> <h1>Hello<s:if test="%{user.name != null}"> <s:property value="user.name" /> <hr/> <h2>Other Details</h2> <h4>Your Name : <s:property value="user.name" /></h4> <h4>Your Age : <s:property value="user.age" /></h4> <h4>Your Gender : <s:property value="user.gender" /></h4> <h4>Your Zip : <s:property value="user.zip" /></h4> <hr/> </s:if> <s:else> World</h1> <p>User List</p> <hr/> <s:if test="users.size > 0"> <table> <s:iterator value="users"> <tr id="row_<s:property value="id"/>"> <td width="200"> <a href="/hello/hello.action?name=<s:property value='name'/>"><s:property value="name" /></a> </td> <td width="50"> <s:property value="zip" /> </td> <td width="20"> <s:property value="age" /> </td> <td width="5"> <s:property value="gender" /> </td> </tr> </s:iterator> </table> </s:if> </s:else> </body> </html>
Modify the Spring configuration to bind everything together
Modify the existing applicationContext.xml
to include JPA related configuration.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- Enable JPA Support --> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <!-- Define EntityManagerFactory and Datasource --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="database" value="MYSQL" /> <property name="showSql" value="true" /> </bean> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/examples" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean> <!-- Define Transaction Manager --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <!-- Define the UserDao --> <bean id="userDao" class="com.rajandesai.example.webapp.dao.jpa.impl.UserDaoImpl" scope="singleton" /> <!-- Define the UserService --> <!-- enable the configuration of transactional behavior based on annotations --> <bean id="userService" class="com.rajandesai.example.webapp.service.impl.UserServiceImpl" scope="singleton"> <property name="userDao" ref="userDao" /> </bean> <!-- Example of SAF2 action instantiated by Spring --> <bean id="helloAction" class="com.rajandesai.example.webapp.action.HelloAction" scope="prototype"> <property name="userService" ref="userService" /> </bean> </beans>
The helloAction
bean is defined with the prototype
scope. This ensures that every request is handled by a new action instance. UserDao
and UserService
are defined with the singleton
scope. UserService
is injected in to the action using application context.
tx:annotation-driven
declaration switches on the transactional behavior of the interfaces defined to be @Transactional
. The Spring team’s recommendation is that you only annotate concrete classes with the @Transactional
annotation, as opposed to annotating interfaces. Please read Transaction Management in Spring 2.5.x for complete details. This example uses org.springframework.jdbc.datasource.DriverManagerDataSource
as the datasource for the EntityManager
. You can easily replace that with org.apache.commons.dbcp.BasicDataSource
if you want to use the Apache commons DBCP connection pool.
Finishing it off…
We are almost done. There is no change to the existing web.xml
file, but we need to configure JPA before actually deploying the application.
Create a file called persistence.xml
under META-INF
to configure the JPA.
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> <persistence-unit name="webapp"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.format_sql" value="true"/> <property name="hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence>
Test Drive
Deploy your application by executing mvn -e jetty:run
from your project directory.
-
First simple http://localhost:8080/hello/hello.action should show you the following view.
-
The following request should add the new user
Jane Doe to the database: http://localhost:8080/hello/hello.action?name=Jane%20Doe&zip=02451&age=25&gender=F.
-
And this request should add the user John Deer in the database.
-
You can get the list of users added to the database by sending a simple request to the application: http://localhost:8080/hello/hello.action.
- clicking on the user’s name send this request to the application: http://localhost:8080/hello/hello.action?name=Jane%20Doe.
Conclusion
This completes the Hello World application that uses Struts framework for web presentation, JPA and Hibernate for data persistence and Spring as the light weight container that binds everything together. I have skipped unit tests from this example to keep it simple for now. I’ll add the unit tests to this application next.
Source code for this project can be downloaded here
Discussion
No comments yet.