Saturday, January 29, 2011

Java Persistence (SpringFramework + JPA + Hibernate)

Spring Framework simplifies adding persistence to java project with excellent declarative transaction  support  (no more begin and end transaction) and with exception translation (less try catch glue code) making the code less verbose. I think with annotations support starting from Spring 2.5 release brings more of the configuration into POJO and less xml making code more readable. I know the annotation/xml is personal choice in my experience I prefer annotation over xml.

Before starting on persistence with Spring It is worth while to read up on JPA and Spring Persistence Support , Hibernate Persistence API and Spring Transaction Support

I will be using all of what I mentioned above during this example. First step would be create a eclipse workspace and set up a maven java project (my preferred project structure) refer to my previous blog post for that. You can download the complete source code  at the end of this blog.

I have listed the complete spring context file here and would like spend a little time on few of the key components.
<?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:context="http://www.springframework.org/schema/context"
 xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:tx="http://www.springframework.org/schema/tx"
 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/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
  http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-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="spring.jpa.domain,spring.jpa.dao,spring.jpa.service" />
  <bean id="entityManagerFactory"
  class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="hsldbDS" />
  <property name="jpaPropertyMap">
   <map>
    <entry key="hibernate.jdbc.fetch_size" value="100"></entry>
    <entry key="hibernate.jdbc.batch_size" value="100"></entry>
    <entry key="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"></entry>
    <entry key="hibernate.cache.use_query_cache" value="true"></entry>
   </map>
  </property>
  <property name="jpaVendorAdapter">
   <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="false" />
    <property name="generateDdl" value="false" />
   </bean>
  </property>
 </bean>
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="dataSource" ref="hsldbDS"></property>
  <property name="entityManagerFactory" ref="entityManagerFactory"></property>
 </bean>
 <!-- if a class has @Repository on it, this will translate JPA exceptions 
  to spring exceptions -->
 <bean
  class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"></bean>
 <bean id="hsldbDS" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  <property name="driverClass" value="${driverClass}" />
  <property name="jdbcUrl" value="${url}"></property>
  <property name="user" value="${username}"></property>
  <property name="password" value="${passowrd}"></property>
  <property name="initialPoolSize" value="5"></property>
  <property name="testConnectionOnCheckout" value="true"></property>
 </bean>
 <bean class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="hsldbDS"></property>
 </bean>
 <bean
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="classpath:jdbc.properties"></property>
 </bean>
 <jdbc:initialize-database data-source="hsldbDS"
  enabled="true">
  <jdbc:script location="classpath:dbscripts/createDB.sql" />
 </jdbc:initialize-database>
 <!-- If you would want to use JNDI, You could replace the c3po pool by using 
  the following config <bean id="hsldbDS" class="org.springframework.jndi.JndiObjectFactoryBean"> 
  <property name="jndiName"> <value>java:comp/env/jdbc/hsldbDS</value> </property> 
  </bean> -->
</beans>

Let's look at the following snippet from xml
<context:component-scan  base-package="spring.jpa.domain,spring.jpa.dao,spring.jpa.service" />
It means scan these packages for POJO's with these stereotypes (@Service, @Component, @Entity, @Repository etc ) and register them as spring beans, If you woule like more details on this  refer to this excellent article.  

Let's move on to the other key spring bean that we configured Entity Manger Factory (key JPA component)
<bean id="entityManagerFactory"   class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

This bean is the spring implementation of  JPA Entity Manager Factory, It is used by application to obtain a application-managed entity managers (JPA spec's , 5.4) . We are using Hiberanate persistence provider and also spring implementation of JPA transaction manager, As a side note make sure you have a persistence.xml in your project under (META-INF directory) otherwise container would throw an exception. I do not want to go through the entire configuration that would take up the entire blog.

I think that's just about covers the configuration part now let's use this configuration in simple example. I have listed a partial domain object that we would be using in this example to perform save/update/delete operations. I would like to bring your attention to the annotation at the top of the class (@Entity) It is a way to identify a persistable domain object and (@Id is used to denote a primary key). I think rest of the annotations are self explanatory


package spring.jpa.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@Entity(name="spring.jpa.domain.Person")
@Table(name="PERSON")
public class Person 
{
   @Id
   @SequenceGenerator(name="personSeq", sequenceName="PERSON_SEQ")
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="personSeq")
   private Integer id;

Now let's demonstrate how to persist this domain object using DAO with @Repository annotation. If you annotate your class with @Repository, spring container will auto-detect and auto-wire the class as spring bean.  The other annotation in the class @PersistenceContext will enable spring container to inject the EntityManager that will be used to save/update/delete any domain object that is configured in the persistence context.

I use a concrete class for my DAO not an interface, It is just personal preference, I have only concrete implementation of the DAO and I think interface would be an over kill. If you look at the save operation that is used to persist this object it is very clean no glue code( no try catch block), You might ask where is the transactional part by design I prefer my service component to handle transactions.

package spring.jpa.dao;

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

import org.springframework.stereotype.Repository;

import spring.jpa.domain.Person;

@Repository
public class SpringJPADAO
{

@PersistenceContext
EntityManager em;
public Person save(Person person)
{
this.em.persist(person);
return person;
}


Let's look at the service object I am using in the example, I would like to bring your attention to @Transactional annotation (If you annotate it at a class level spring will wrap all the public methods in the class with a transaction, You can do this at method level if you prefer that way). If you look at the service method (although it is rather trivial in comparison) only part that is in the code is some thing to do with business logic (free of glue code).


package spring.jpa.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import spring.jpa.dao.SpringJPADAO;
import spring.jpa.domain.Person;

@Service
@Transactional
public class SpringJPAService
{
@Autowired
SpringJPADAO dao;
public void save(Person person)
{
  dao.save(person);
}


You can use hibernate tools to reverse generate the domain objects based of db schema OR better yet can use hibernate maven plugin to do this.


You can download the complete source code from here

No comments:

Post a Comment