Chapter 10: JPA Integration

From Documentation

Overview

In previous chapters, we mimic a database with a static list as follows:

public class UserInfoServiceImpl implements UserInfoService,Serializable{

	
	static protected List<User> userList = new ArrayList<User>();  
	static{
		userList.add(new User("anonymous","1234","Anonymous","[email protected]"));
		userList.add(new User("admin","1234","Admin","[email protected]"));
		userList.add(new User("zkoss","1234","ZKOSS","[email protected]"));
	}
	
	/** synchronized is just because we use static userList in this demo to prevent concurrent access **/
	public synchronized User findUser(String account){
		int s = userList.size();
		for(int i=0;i<s;i++){
			User u = userList.get(i);
			if(account.equals(u.getAccount())){
				return User.clone(u);
			}
		}
		return null;
	}
	
...
}

Java Persistence API (JPA) is a POJO-based persistence specification. It offers object-relational mapping solution to enterprise Java applications. In this chapter, we don't create new applications but re-write data persistence part based on chapter 9 with JPA. We will create a simple database with HSQL and implement a persistence layer in DAO (Data Access Object) pattern to encapsulate all database related operations. We also have to annotate all entity classes that will be stored in the database with JPA annotations. To make the example close to a real application, we keep the Spring framework and demonstrate how to integrate Spring with JPA.

Configuration

Maven

For using database, JPA, and integration of JPA and Spring, we should add following dependencies based on chapter 9's configuration: (We add spring-web and cblib for Spring framework which is explained in chapter 9.)


	<properties>
		<zk.version>6.5.1</zk.version>
		<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
		<packname>-${project.version}-FL-${maven.build.timestamp}</packname>
		<spring.version>3.1.2.RELEASE</spring.version>
	</properties>

...
		<!-- Spring 3 dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2.2</version>
		</dependency>
		<!-- JPA(Hibernate) and HSQL dependencies -->
		<dependency>
	  		<groupId>org.hibernate</groupId>
	  		<artifactId>hibernate-entitymanager</artifactId>
	  		<version>4.0.0.Final</version>
	  	</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.2.6</version>
		</dependency>
...
  • Line 5, 16~17: Spring provides a module to integrate several ORM (Object Relation Mapping) frameworks, integrating JPA requires this dependency.
  • Line 27~28: There are various implementations of JPA specification, we choose Hibernate's one which is the most popular.
  • Line 32~33: For using HSQL, we should add its JDBC driver.

Persistence Unit Configuration

We should describe persistence unit configuration in an XML file called persistence.xml and we need to specify name, transaction-type, and properties. The properties are used by persistence provider (Hibernate) to establish database connection and setup vendor specific configurations.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

	<persistence-unit name="myapp" transaction-type="RESOURCE_LOCAL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<properties>
			<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
			<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
			<property name="hibernate.connection.username" value="sa" />
			<property name="hibernate.connection.password" value="" />
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.connection.url" value="jdbc:hsqldb:file:data/store" />
			<property name="hibernate.hbm2ddl.auto" value="create" />
		</properties>
	</persistence-unit>
</persistence>


Deployment Descriptor

You don't have to add any special configuration for JPA to work. Here we add a Spring provided OpernEntityMangerInViewFilter to resolve an issue caused by lazy-fetching in one-to-many mapping. Please refer to Developer's Reference for this issue in more detail.

Extracted from web.xml

...
	<!-- Spring configuration -->
	<!-- Initialize spring context -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- Enable webapp Scopes-->
	 <listener>
    	<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>

	<filter>
		<filter-name>OpenEntityManagerInViewFilter</filter-name>
		<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>OpenEntityManagerInViewFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
...

Entity Annotation

Before storing objects into a database, we should specify OR (object-relation) mapping for Java classes with meta data. After JDK 5.0, we can specify OR mapping in annotations instead of XML files. JPA supports configuration by exception which means that it defines default for most cases of application and users only need to override the configuration value when it is exception to the default, not necessary.

First, annotate the class with @Entity to turn it into an entity, and annotate the member field for primary key with @Id. All other annotations are optional and we use them to override default values.

Todo class used in todo-list management

@Entity
@Table(name="apptodo")
public class Todo implements Serializable, Cloneable {


	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	Integer id;
	
	boolean complete;
	
	@Column(nullable=false,length=128)
	String subject;
	
	@Column(nullable=false,length=128)
	@Enumerated(EnumType.ORDINAL)
	Priority priority;
	
	@Temporal(TemporalType.TIMESTAMP)
	Date date;
	
	String description;

	//omit getter, setter, and other methods

}

User class used in profile form

@Entity
@Table(name="appuser")
public class User implements Serializable,Cloneable {

	
	@Id
	@Column(nullable=false,length=32)
	String account;
	
	@Column(nullable=false,length=128)
	String fullName;
	
	@Column(nullable=false,length=255)
	String password;
	
	@Column(nullable=false,length=255)
	String email;
	
	@Temporal(TemporalType.TIMESTAMP)
	Date birthday;

	//omit getter, setter, and other methods
}

Spring Beans Configuration

The simplest Spring setup for JPA is to add a LocalEntityManagerFactoryBean which create a EntityManagerFactory for simple deployment environments and specify its persistenceUnitName property.


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
 	<context:component-scan base-package="org.zkoss.tutorial" />
 
 	<!-- jpa(hibernate) configuration -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="myapp"/>
	</bean>
	
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<tx:annotation-driven />
</beans>

DAO Implementation

@Repository
public class TodoDao {

	@PersistenceContext
	private EntityManager em;
	
	@Transactional(readOnly=true)
	public List<Todo> queryAll() {
        	Query query = em.createQuery("from Todo as todo");
	        List<Todo> result = query.getResultList();
        	return result;
	}
	...
}


@Service("todoListService")
@Scope(value="singleton",proxyMode=ScopedProxyMode.TARGET_CLASS)
public class TodoListServiceImpl implements TodoListService {

	@Autowired
	TodoDao dao;
	
	public List<Todo>getTodoList() {
		return dao.queryAll();
	}
	
	...
}