Pages

Friday, July 26, 2013

Unit Testing of JPA Persistence Layer with Spring Framework

In many cases, an application needs to save and read data from database. It's a common test case that you want the data persistence/access layer works as expected. Running a few JUnit tests seems a common practice, however a dependency to an external database makes such JUnit tests less portable or less stable (imaging the tests can simply fail because database is somehow changed externally). This can cause more headache if a continuous integration is required to run such JUnit tests automatically and periodically.

Fortunately, if you are building such an application using Spring Framework and using JPA as a way to persistent/access your data (or using Spring Data), there are some easy ways to perform such JUnit tests against your data access layer.

The basic idea to implement such tests is to use an in-memory database. Such an in-memory database can be constructed with a set of predefined SQL statements/scripts.

Here's a simple example to illustrate the idea. This example is based on the previous Simple JPA example using Spring Framework.

In that example, we've created a data access object (DAO) for the entity User - UserDao. Here we will create the JUnit test case for the two methods (getNameStartsWith, and save). The test code looks like below:

[UserDaoTest.java]
package com.mytechtip.example.springjpatest;

import static org.junit.Assert.*;

import java.util.Calendar;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(org.springframework.test.context.junit4.SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:beans-test.xml")
@Transactional
public class UserDaoTest {
  
  @Autowired
  UserDao userDao;

  @Before
  public void setUp() throws Exception {
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testGetNameStartsWith() {
    
    List<User> list = userDao.getNameStartsWith("J");
    assertEquals(1, list.size());
    User user = list.get(0);
    System.out.println(user.getId()+":"+user.getName() + ":" 
        + user.getDob()+":"+user.getAddress());
    assertEquals("Jack", user.getName());
  }

  @Test
  public void testSave() {
    User u = new User();
    u.setAddress("my address");
    u.setName("John Smith");
    
    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(2000, 0, 1);
    u.setDob(cal.getTime());
    
    userDao.save(u);
    
    List<User> list = userDao.getNameStartsWith("J");
    assertEquals(2, list.size());
    User user = list.get(1);
    System.out.println(user.getId()+":"+user.getName() + ":" 
        + user.getDob()+":"+user.getAddress());
    assertEquals("John Smith", user.getName());
    assertEquals(cal.getTime(), u.getDob());
  }
}

The annotations of the test class plays an important role. It specifies a different beans configuration file ("classpath:beans-test.xml") just for testing. This file should exist in your test source folder as it is only used for unit testing. The content of the beans configuration file is shown as below:

[beans-test.xml]
<?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:tx="http://www.springframework.org/schema/tx" 
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/tx 
  http://www.springframework.org/schema/tx/spring-tx.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/jdbc
  http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
  ">

  <context:component-scan base-package="com.mytechtip.example.springjpatest">
  </context:component-scan>

  <tx:annotation-driven />

  <bean class="org.springframework.orm.jpa.JpaTransactionManager"
    id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <bean id='entityManagerFactory'
    class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
    <property name="persistenceUnitName" value="spring-jpa-test" />
    <property name='dataSource' ref='dataSource' />
    <property name="jpaPropertyMap">
      <map>
        <entry key="eclipselink.weaving" value="false" />
      </map>
    </property>
  </bean>

  <jdbc:embedded-database id="dataSource">
    <jdbc:script location="classpath:create-db.sql" />
    <jdbc:script location="classpath:create-data.sql" />
  </jdbc:embedded-database>
</beans>

Compared with the previous beans configuration file that specifies an actual hsqldb data source, this beans configuration file creates embedded database using two sql files as the data source. The contents of the two sql files are shown as below. They can be edited in accordance with the junit test case mentioned above.

[create-db.sql]
insert into user(id, name, dob) values (1, 'Jack', '1999-12-31');

[create-data.sql]
CREATE TABLE user 
(ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT BY 1) NOT NULL, 
ADDRESS VARCHAR(255), 
DOB DATE, 
NAME VARCHAR(255), 
PRIMARY KEY (ID));

The folder/file structure of the project is like the following. This is a maven project which has all the dependency defined in the pom.xml. And now you can download this example project from github and try it out by yourself.

Thursday, July 11, 2013

Simple JPA example using Spring Framework

The hsqldb table "USER"
JPA (Java Persistence API) is already an old topic. It has been widely used to save and access the data to and from the database due to its simplicity.

Spring Framework supports JPA very well. This post will show you a very simple example of using JPA under Spring Framework.

Let's say we want to save and access some information of  users - a very common use case. We assume a hsqldb database table "USER" with the fields of "ID", "ADDRESS", "DOB", "NAME" for simplicity. This is illustrated in the figure with one record inserted.

Creating the above table (hsqldb) can be done by the following SQL
 CREATE TABLE user (  
   ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1  INCREMENT BY 1) NOT NULL,   
   ADDRESS VARCHAR(255),   
   DOB DATE,   
   NAME VARCHAR(255),   
   PRIMARY KEY (ID))  

For this table, we create the following java entity class which some JPA annotations (such as @Entity @Table, @Id and etc).
package com.mytechtip.example.springjpatest;

import java.io.Serializable;
import java.lang.String;
import java.util.Date;
import javax.persistence.*;

/**
 * Entity implementation class for Entity: User
 *
 */
@Entity
@Table(name="user")
public class User implements Serializable {

 private static final long serialVersionUID = 1L;

 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private long id;
 private String name;
 private String address;
 
 @Temporal(TemporalType.DATE)
 private Date dob;

 public User() {
  super();
 }   
 public long getId() {
  return this.id;
 }

 public void setId(long id) {
  this.id = id;
 }   
 public String getName() {
  return this.name;
 }

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

 public void setAddress(String address) {
  this.address = address;
 }   
 public Date getDob() {
  return this.dob;
 }

 public void setDob(Date dob) {
  this.dob = dob;
 }
}

You also need a file called "persistence.xml" placed in folder META-INF. The file contains the following content. It just defines the persistence provider (we use eclipselink) and the entity class

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
 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_2_0.xsd">
 <persistence-unit name="spring-jpa-test" transaction-type="RESOURCE_LOCAL">
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <class>com.mytechtip.example.springjpatest.User</class>
 </persistence-unit>
</persistence>

Then we can define some data access operations such as save, or query. Such methods can be grouped in a Data Access Object (DAO) class as shown below. This UserDao class just defines two operations for demonstration purpose. Some notes below:
  • This class is annotated with @Repository so the Spring Framework can be configured to scan the class for auto wired stuff and etc. 
  • The methods are annotated with @Transactional so it ensures ALL or NOTHING operations (especially for write operations)
  • EntityManager em is annotated with @PersistenceContext so Spring Framework can inject the container-managed entity manager.

package com.mytechtip.example.springjpatest;

import java.util.List;

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

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
public class UserDao {
  
  private static final int ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
  
  @PersistenceContext
  EntityManager em;
  
  @Transactional
  public User save(User u) {
    // some business logic
    if (u.getName()==null) {
      throw new IllegalArgumentException("Name can not be null");
    }
    if (u.getDob()==null) {
      throw new IllegalArgumentException("Dob can not be null");
    }
    if ((System.currentTimeMillis()-u.getDob().getTime())< 10 * ONE_YEAR) {
      throw new IllegalArgumentException("Must be 10+ year older");
    }
    return em.merge(u);
  }

  @Transactional
  public List<User> getNameStartsWith(String namePrefix) {
    String query = "select u from User u where u.name like :prefix " +
        "order by u.name";
    Query q = em.createQuery(query);
    q.setParameter("prefix", namePrefix+ "%");
    return q.getResultList();
  }
}

Now, it's time to wire them up using a beans configuration file as most of Spring Framework applications do. The content of the beans.xml is as follows.

<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/tx 
  http://www.springframework.org/schema/tx/spring-tx.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context.xsd
  ">

  <!-- Enable the component scan (auto wiring etc) for the following package -->
  <context:component-scan base-package="com.mytechtip.example.springjpatest" />
    
  <!-- Make sure the following is specified to enable transaction  -->
  <tx:annotation-driven />
    <bean class="org.springframework.orm.jpa.JpaTransactionManager" 
        id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!--  This defines the entity manager factory with some custom properties -->
  <bean id='entityManagerFactory' 
      class='org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean'>
    <property name="persistenceUnitName" value="spring-jpa-test"/>
    <property name='dataSource' ref='dataSource' />
    <property name="jpaPropertyMap">
        <map>
          <entry key="eclipselink.weaving" value="false"/>
        </map>
    </property>
  </bean>

  <!-- This defines the hsqldb data source -->
  <bean id='dataSource' 
      class='org.springframework.jdbc.datasource.DriverManagerDataSource'>
    <property name='driverClassName' value='org.hsqldb.jdbc.JDBCDriver' />
    <property name='url' value='jdbc:hsqldb:file:spring_jpa_test_db' />
    <property name='username' value='sa' />
    <property name='password' value='' />
  </bean>
</beans>

We've now done the most work. Next step is how to run it to see whether it works. Here comes the following java code that can test the methods we've created. It basically saves a user record and do a query to find list of users with name starting with "J".

package com.mytechtip.example.springjpatest;

import java.util.Calendar;
import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
  
  public static void main(String[] args) {
    ClassPathXmlApplicationContext context = 
        new ClassPathXmlApplicationContext("beans.xml");
    
    UserDao dao = context.getBean(UserDao.class);
    
    User u = new User();
    u.setAddress("my address");
    u.setName("John Smith II");
    
    Calendar cal = Calendar.getInstance();
    cal.set(2001, 0, 1);
    u.setDob(cal.getTime());
    
    dao.save(u);
    
    List<User> list = dao.getNameStartsWith("J");
    for (User user : list) {
      System.out.println(u.getName());
    }
    context.close();
  }
}

If you want to try the example, now you can download the entire project from github.