- Introspecting my wandering mind - http://rajandesai.com/blog -

How to add persistence to Struts2 + Spring application using JPA and Hibernate

Introduction

In my previous post, How to integrate Spring Framework with a Struts2 application [1], 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:

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 [2] 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 [3] 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.

  1. First simple http://localhost:8080/hello/hello.action should show you the following view.
    hello-world-with-spring-1
  2. 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.
    twisted-hello-world-spring-1
  3. And this request should add the user John Deer in the database.
    twisted-hello-world-spring-2
  4. 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.
    twisted-hello-world-spring-3
  5. clicking on the user’s name send this request to the application: http://localhost:8080/hello/hello.action?name=Jane%20Doe.

  6. twisted-hello-world-spring-4

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 [4]