Jersey, Spring and JPA

Introduction

This article presents an integration of Jersey, Spring and JPA.

Integration is always a challenge.  It can consume a great deal of development time.  I enjoyed reading Paul Sandoz’s article Jersey and Spring.  In this article Paul describes the deployment of the Jersey’s integrated support for Spring.

Here we extends Paul’s project to include a JPA entity.  This integration took more effort than expected which lead me to write this article which provides a simple exemplar of this integration. I’m extending Paul’s solution to include a JPA portion following and presenting it using his basic essay structure.

Throughout the article there are code snippetts which can be incorpate into other projects or one can build the project as described. Note that there is a jar file of the project’s code with POM so you can build the project from that archive if you prefer.

Creating the Basic Web Application

To begin this exercise there is a Maven2 archetype that we can use to set-up our project. This archetype sets up a Jersey project which we use as the starting point for our example. We will be making changes to the POM, and web.xml plus adding files including applicationResource.xml, persistence.xml and supporting classes. Below is the command to create the archetype:

mvn archetype:generate -DarchetypeCatalog=http://download.java.net/maven/2

The GroupId and ArtifactId is for your local Maven Repository. For the example the following for used:

  • Define value for groupId: : com.persistent
  • Define value for artifactId: : jerseySpringJPA
  • Define value for version: 1.0-SNAPSHOT: : <CR>
  • Define value for package: com.persistent: : com.persistent.rest

The basic project structure we are creating is:

Changes to the POM

 Changes need to be made to add the Jersey-Spring functionality, as described in Paul Sandoz article, and the JPA libraries.  Adding JPA to the POM caused some problems which is part of the reason for the article. 

 I prefer using Jetty so I replaced the GlassFish plugin and removed the GlassFish dependencies:

<plugin>
  	<groupId>org.mortbay.jetty</groupId>
  	<artifactId>maven-jetty-plugin</artifactId>
  	<version>6.1.10</version>
  </plugin>

The versions of the artifacts are given below. Use this properties section to replace the one existing in the POM

<properties>
 	<spring.version>2.5.5</spring.version>
  	<hibernate.version>3.3.2.GA</hibernate.version>
  	<commons-dbcp.version>1.2.2</commons-dbcp.version>
  	<commons-logging.version>1.0.4</commons-logging.version>
  	<jersey-version>1.0.2</jersey-version>
 </properties>

 The following Jersey-Spring artifacts are added to replace the Jersey artifacts in the architype:

<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-server</artifactId>
  <version>${jersey-version}</version>
</dependency>
<dependency>
  <groupId>com.sun.jersey.contribs</groupId>
  <artifactId>jersey-spring</artifactId>
  <version>${jersey-version}</version>
</dependency>
</pre>
<p>Next we add the artifacts required by JPA.  In here there are a couple of changes that need to be made to allow the artifacts to work together.  Hibernate and Jersey referent versions of ASM and CGLIB.  This is the main difficulty when trying to integrate these packages.</p>
<pre class="brush:xml; collapse:false; wrap-lines:true;">
<properties>
 	<spring.version>2.5.5</spring.version>
  	<hibernate.version>3.3.2.GA</hibernate.version>
  	<commons-dbcp.version>1.2.2</commons-dbcp.version>
  	<commons-logging.version>1.0.4</commons-logging.version>
  	<jersey-version>1.0.2</jersey-version>
 </properties>

 The following Jersey-Spring artifacts are added to replace the Jersey artifacts in the architype:

<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-server</artifactId>
  <version>${jersey-version}</version>
</dependency>
<dependency>
  <groupId>com.sun.jersey.contribs</groupId>
  <artifactId>jersey-spring</artifactId>
  <version>${jersey-version}</version>
</dependency>

Next we add the artifacts required by JPA.  In here there are a couple of changes that need to be made to allow the artifacts to work together.  Hibernate and Jersey referent versions of ASM and CGLIB.  This is the main difficulty when trying to integrate these packages.  The additions and changes are identified with comments below.

		<!-- Adding in JPA With It's Requirements -->
<dependency>
	<groupId>commons-logging</groupId>
	<artifactId>commons-logging</artifactId>
	<version>${commons-logging.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>${spring.version}</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring</artifactId>
	<version>${spring.version}</version>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>javax.persistence</groupId>
	<artifactId>persistence-api</artifactId>
	<version>1.0</version>
</dependency>
<dependency>
	<groupId>commons-dbcp</groupId>
	<artifactId>commons-dbcp</artifactId>
	<version>${commons-dbcp.version}</version>
	<scope>runtime</scope>
</dependency>
<!--Explicitly add -->

<!--
	change cglib-nodep Farrukh Najmi From net.java.dev.jersey.users Dec.
	10, 2007
-->
<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib-nodep</artifactId>
	<version>2.1_3</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>${hibernate.version}</version>
	<!--
		Explicitly remove: See:

http://blog.interface21.com/main/2007/06/11/asm-version-incompatibilities-using-spring-autowired-with-hibernate/

	-->
	<exclusions>
		<exclusion>
			<groupId>asm</groupId>
			<artifactId>asm</artifactId>
		</exclusion>
		<exclusion>
			<groupId>asm</groupId>
			<artifactId>asm-attrs</artifactId>
		</exclusion>
		<exclusion>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.6</version>
</dependency>
<!-- JPA Additions end. -->

The Entire POM file can be viewed here:

<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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>example.jersey.spring</groupId>
	<artifactId>example-spring-jersey</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>example-spring-jersey Jersey Webapp</name>
	<repositories>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Maven 2 Repository</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
		<repository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Maven 1 Repository (legacy)</name>
			<url>http://download.java.net/maven/1</url>
			<layout>legacy</layout>
		</repository>
	</repositories>
	<build>
		<finalName>jersey</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<inherited>true</inherited>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>6.1.10</version>
			</plugin>
		</plugins>
	</build>
	<properties>
		<spring.version>2.5.5</spring.version>
		<hibernate.version>3.3.2.GA</hibernate.version>
		<commons-dbcp.version>1.2.2</commons-dbcp.version>
		<commons-logging.version>1.0.4</commons-logging.version>
		<jersey-version>1.0.2</jersey-version>
	</properties>

	<dependencies>
		<!-- Adding in JPA With It's Requirements -->
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${commons-logging.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring</artifactId>
			<version>${spring.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>javax.persistence</groupId>
			<artifactId>persistence-api</artifactId>
			<version>1.0</version>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>${commons-dbcp.version}</version>
			<scope>runtime</scope>
		</dependency>
		<!--Explicitly add -->
		<!--
			change cglib-nodep Farrukh Najmi From net.java.dev.jersey.users Dec.
			10, 2007
		-->
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib-nodep</artifactId>
			<version>2.1_3</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
			<!--
				<artifactId>hibernate</artifactId> <version>3.2.5.ga</version>
				Explicitly remove: See:

http://blog.interface21.com/main/2007/06/11/asm-version-incompatibilities-using-spring-autowired-with-hibernate/

			-->
			<exclusions>
				<exclusion>
					<groupId>asm</groupId>
					<artifactId>asm</artifactId>
				</exclusion>
				<exclusion>
					<groupId>asm</groupId>
					<artifactId>asm-attrs</artifactId>
				</exclusion>
				<exclusion>
					<groupId>cglib</groupId>
					<artifactId>cglib</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>
		<!-- JPA Additions end. -->
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-server</artifactId>
			<version>${jersey-version}</version>
		</dependency>
		<dependency>
			<groupId>com.sun.jersey.contribs</groupId>
			<artifactId>jersey-spring</artifactId>
			<version>${jersey-version}</version>
		</dependency>
	</dependencies>
</project>

 We are offering a Rest service which uses a JPA Entity on the back end.  We’ll start with a simple JPA configuration for an entity called Person utilizing annotations in the creation of the Person entity.

JPA Code and Configuration

To setup JPA we will do the following:

  1. Define an entity bean
  2. Define a Service for the Entity
  3. Configure the appropriate elements in the applicationContext.xml

Defining the Entity Bean

Here is the annotated Person entity.  The aim of the article is creating a simple example of the integration of these technologies rather than providing a fancy application.  The Person Entity is a straight forward POJO with two fields of data: name and age plus the id.  Most of the work happens in just a few lines of code:

@Entity
@Table(name = "PERSON")
@NamedQueries( { @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p"),
		@NamedQuery(name = "Person.findPerson", query = "SELECT p FROM Person p where p.name=:name and p.age=:age")
	})
public class Person {
	private int id;
	private String name;
	private int age;
package com.persistent.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "PERSON")
@NamedQueries( { @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p"),
		@NamedQuery(name = "Person.findPerson", query = "SELECT p FROM Person p where p.name=:name and p.age=:age")
	})
public class Person {
	private int id;
	private String name;
	private int age;

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

	@Id
	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getAge() {
		return age;
	}
}

 

Defining the Service

We define the following service interface and JPA Implementation. The implementation will be annotated as a @Resource which defines the class as both a Spring bean as well as providing Spring support for database exceptions. We have marked the whole class as read-only transaction and have annotated the save method as read-only false to allow the saving of the Person object.

The Person Interface

package com.persistent.service;

import java.util.List;

import com.persistent.entity.Person;

public interface PersonService {
	public boolean save(Person person);
	public List<Person> getAll();
	public Person getById(int id);
	public boolean delete(Person person);
	public boolean update(Person person);
	public Person findPerson(Person person);
}

The Person JPA Implementation

package com.persistent.service.jpa;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.persistent.entity.Person;
import com.persistent.service.PersonService;

@Service("personService")
public class PersonServiceJpa implements PersonService {

	private EntityManager entityManager;

	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public EntityManager getEntityManager() {
		return entityManager;
	}

	@Transactional(readOnly = true)
	public Person getById(int id) {
		// TODO Auto-generated method stub
		return entityManager.find(Person.class, id);
	}
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public List<Person> getAll() {
		Query query = entityManager.createNamedQuery("Person.findAll");
		List<Person> persons = null;
		persons = query.getResultList();
		return persons;
	}

	@Transactional(readOnly=false, propagation=Propagation.REQUIRED)
	public boolean save(Person person) {

		entityManager.persist(person);
		entityManager.flush();

		return true;
	}
	@Transactional(readOnly=false, propagation=Propagation.REQUIRED)
	public boolean update(Person person) {
		entityManager.merge(person);
		entityManager.flush();
		return true;
	}
	@Transactional(readOnly=false, propagation=Propagation.REQUIRED)
	public boolean delete(Person person) {
		person = entityManager.getReference(Person.class, person.getId());
		if (person == null)
			return false;
		entityManager.remove(person);
		entityManager.flush();
		return true;
	}

	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	public Person findPerson(Person person) {
		Person result = null;
		Query queryFindPerson = entityManager.createNamedQuery("Person.findPerson");
		queryFindPerson.setParameter("name", person.getName());
		queryFindPerson.setParameter("age", person.getAge());
		List<Person> persons = queryFindPerson.getResultList();
		if(persons.size() > 0) {
			result = persons.get(0);
		}
		return result;
	}
}

Configuring the applicationContext.xml

Note that the applicationContext.xml file contains the specifics about your database configuration. It is currently configured to work with a MySQL 5.0 Database. Create the database jpa and create the Person table as follows:

mysql>create database jpa;

CREATE TABLE PERSON(
	ID INT(5) PRIMARY KEY NOT NULL AUTO_INCREMENT,
	NAME VARCHAR(50) NOT NULL,
	AGE INT(3) NOT NULL);

Also if you have chosen a different directory names you will need to adjust the directories that the component-scan is performed upon.

<?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:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
			http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	   <!--  Scan for both Jersey Rest Annotations a -->
       <context:component-scan base-package="com.persistent.rest,com.persistent.service,com.persistent.service.jpa"/>
       <context:annotation-config />
       <tx:annotation-driven />
       <bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource"
		p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/jpa"
		p:username="user" p:password="password" />

	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
		p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaAdapter">
		<property name="loadTimeWeaver">
			<bean
				class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
		</property>
	</bean>
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
		p:entityManagerFactory-ref="entityManagerFactory" />
	<bean id="jpaAdapter"
		class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
		p:database="MYSQL" p:showSql="true" />
   </beans>

The persistence.xml file, to be created in the resources/META-INF directory, is:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
		version="1.0">

	<persistence-unit name="testa" transaction-type="RESOURCE_LOCAL">
        <class>com.persistent.entity.Person</class>
    </persistence-unit>

</persistence>

The web.xml

The web.xml file needs to be changed to add the context for the Springframework and JerseySpring.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <url-pattern>/webresources/*</url-pattern>
    </servlet-mapping>
</web-app>

Finally the Rest Class that makes this all happen is below:

package com.persistent.rest;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.persistent.entity.Person;
import com.persistent.service.PersonService;

// The Java class will be hosted at the URI path "/myresource"
@Path("/myresource")
@Component
@Scope("request")
public class MyResource {
	@Autowired
	PersonService personService;

	// The Java method will process HTTP GET requests
	@GET
	// The Java method will produce content identified by the MIME Media
	// type "text/plain"
	@Produces("text/plain")
	public String getIt() {
		Person person = new Person();
		person.setName("David Sells");
		person.setAge(99);

		addIfDoesNotExist(person);

		StringBuffer buffer = new StringBuffer();

		List<Person> persons = personService.getAll();
		for (Person person2 : persons) {
			buffer.append(person2.getName()).append(":").append(person2.getAge())
					.append("\n");
		}

		return "Hi there: "+buffer.toString();
	}
	private void addIfDoesNotExist(Person person) {
		if(personService.findPerson(person) == null) {
			personService.save(person);
		}
	}
}

Compiling and Running the Application

To compile and run the program in a Jetty server execute the following commands in the application’s root directory (where the POM is located):

mvn clean install
mvn jetty:run-war

Alternatively you can take the war file from the target directory and deploy it in an alternate application web server such as Apache Tomcat. Once the application is running you can make a soap request with the following Curl invocation:

   curl -v http://localhost:8080/jerseySpringJPA/webresources/myresource

Or

In a browser:


http://localhost:8080/jerseySpringJPA/

and select the Jersey resource link in the web page.

The expected output is:

Hi there: David Sells:99

Conclusion

This concludes the article.  You now, hopefully, having a working example which has integrated the functionality of Jersey, Spring and JPA.  The annotations and implicitly applied aspect-oriented programming dramatically reduces the effort required to provide these services in an enterprise environment. If you have any question or comments do not hesitate to write.

Articles of Interest

The Source Code

The complete code listing is here.

About The Author

David Sells is a computer consultant in Toronto, Ontario who specializing in Java Enterprise Development. He has  provided innovative solutions for clients including: IBM, Goldman Sachs, Merrill Lynch and Deutsche Bank.

He also holds the following certifications:

  • Sun Certified Enterprise Architect for the Java Platform, Enterprise Edition 5 (2009)
  • Sun Certified Web Component Developer
  • Sun Certified Java Programmer.

Contact: david@persistentdesigns.com