Problem

Majority of enterprise applications use database as a persistence layer. JPA framework allows developers to manage relational data in their J2SE and J2EE applications. While knowledge about JPA domain modeling techniques is widespread and available, there is little known about persistence layer testing. In this post I will describe the process of setting up an environment for an application integration testing using JUnit, Spring Framework and Maven.

The standard Maven 2 directory layout

The standard Maven 2 directory structure can be seen below.

Maven Directory Layout


In the project home directory goes the pom.xml and two subdirectories – src for source code and target for compilation output. The src directory has two subdirectories, main – all files related to the application being developed go here and test – all test related files go here.

Naturally, this layout might be different, most likely having more elements – this depends on the archetype used during project inception, but, nevertheless, the layout above is an absolute minimum. The test directory have two subdirectories – java – with all unit tests and resources – with resources, which are used for testing, but are not deployed.
Having two separate directories for application and test allows us to have two, completely separated configuration files, so that testing environment was completely independent from development environment. It is possible, to develop using one database instance and test on the other, or even use MySQL for development and HSQLDB for testing, if required.

Configuration files

persistence.xml

The persistence.xml file defines a persistence-unit, and according to specification, has to be included an the META-INF directory of a JAR file that contains entities. However, Spring gives a little bit more room here and my approach is to put it in a config subdirectory of test/resources. The minimum persistence.xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence version="1.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_1_0.xsd
  ">

  <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL"/>

</persistence>

defines only the persistence-unit name, everything else is configured in Spring Framework configuration file – applicationContext.xml, which goes also to a config directory.

applicationContext.xml

Spring Framework configuration file – applicationContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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:aop="http://www.springframework.org/schema/aop"
  xmlns:p="http://www.springframework.org/schema/p"
  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
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
  ">

  <!-- Enable processing of PersistenceUnit and PersistenceContext annotations  -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

  <!-- Persistence exception translation for Repositories -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- Enable annotation-driven configuration and auto-detection -->
  <context:annotation-config/>

  <!-- Enable component scan -->
  <context:component-scan base-package="com.bewareofthebear"/>

  <!-- Configure property placeholders for environment-specific properties customization -->
  <context:property-placeholder location="classpath:/properties/*.properties" />

  <!-- Datasource to be used by Hibernate/JPA  -->
  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="${db.driverClassName}"
    p:url="${db.url}"
    p:username="${db.username}"
    p:password="${db.password}"
  />

  <!-- EntityManagerFactory using defined datasource and JPA provider - Hibernate  -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:persistenceXmlLocation="classpath:/config/persistence.xml"
    p:dataSource-ref="dataSource"
  	p:persistenceUnitName="persistenceUnit"
    >
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="jpaProperties">
      <props>
        <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
        <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
        <prop key="hibernate.hbm2ddl.auto">create</prop>
        <prop key="hibernate.format_sql">false</prop>
        <prop key="hibernate.use_sql_comments">false</prop>
      </props>
    </property>
  </bean>

  <!--  Transaction manager -->
  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
    p:entityManagerFactory-ref="entityManagerFactory"
    p:dataSource-ref="dataSource"
  />

  <!-- Use @Transactional annotation on methods -->
  <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

As seen, using persistenceXmlLocation property of EntityManagerFactory I was able to point to my test version of a persistence.xml file. This approach has however one drawback which comes from limitations of JPA’s entities scan. It scans only paths within a persistent unit root url, which is a parent folder of persistence.xml location. Since maven copies content of the resource folder to the target/test-classes folder, than this folder becomes the root url and is scanned during application start-up. As a result of this, all application entities defined under main/java/… will not be scanned and, if test database is different than development database, than tests will fail. There are few ways to solve this problem.

First, it is possible to use an application version of persistence.xml instead of test version, however, this approach has the same drawback, just in opposite direction – all application entities are scanned, but, if there are any entities defined in a test directory than these will not be scanned.

Second solution is to explicitly list required entities in a persistence.xml file:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence version="1.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_1_0.xsd
  ">

  <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
    <class>com.bewareofthebear.domain.model.customer.Customer</class>
    <class>com.bewareofthebear.domain.model.customer.Category</class>
  </persistence-unit>

</persistence>

Which works, but could be time consuming and error prone.

Third solution is to use PersistenceUnitPostProcessors, which will scan all entities and add them to persistence unit.

package com.bewareofthebear.application;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.scannotation.AnnotationDB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;

public class TestPersistenceUnitPostProcessor implements  PersistenceUnitPostProcessor  {
  private final Logger log = LoggerFactory.getLogger( TestPersistenceUnitPostProcessor.class );

  public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
    URL persistenceUnitRootUrl = pui.getPersistenceUnitRootUrl();

    String protocol = persistenceUnitRootUrl.getProtocol();
    String host =  persistenceUnitRootUrl.getHost();
    int port = persistenceUnitRootUrl.getPort();
    String file = persistenceUnitRootUrl.getFile();
    String newFile = file.substring(0, file.lastIndexOf("test-classes")) + "classes/";

    URL url = null;
    try {
      url = new URL(protocol, host, port, newFile);
    } catch (MalformedURLException e) {
      log.warn( "Malformed url: " + url, e );
      return;
    }

    AnnotationDB annotationDB = new AnnotationDB();
    try {
      annotationDB.scanArchives(url);
    } catch (IOException e) {
      log.warn( "Unexpected IOException", e );
      return;
    }

    if (annotationDB.getAnnotationIndex().containsKey("javax.persistence.Entity")) {
      for (String entity : annotationDB.getAnnotationIndex().get("javax.persistence.Entity")) {
        pui.addManagedClassName(entity);
        log.debug( "Adding: {}", entity );
      }
    }
  }

}

PersistenceUnitPostProcessors has to be wired to LocalContainerEntityManagerFactoryBean:

  <property name="persistenceUnitPostProcessors">
    <list><bean class="com.bewareofthebear.application.TestPersistenceUnitPostProcessor"/></list>
  </property>

Now, the configuration is completed, so it is time to move on and create some tests.

Test classes

The basic template for creating test classes:

package com.bewareofthebear.test;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/config/applicationContext.xml"})
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
@Transactional
public class JPATestTemplate {

  @BeforeTransaction
  public void verifyInitialDatabaseState() {
  // logic to verify the initial state before a transaction is started
  }

  @Before
  public void setUpTestDataWithinTransaction() {
  // set up test data within the transaction
  }

  @Test
  @Rollback(true)
  public void modifyDatabaseWithinTransaction() {
  // logic which uses the test data and modifies database state
  }

  @After
  public void tearDownWithinTransaction() {
  //  execute "tear down" logic within the transaction
  }

  @AfterTransaction
  public void verifyFinalDatabaseState() {
  // logic to verify the final state after transaction has rolled back
  }

}
  • @RunWith(SpringJUnit4ClassRunner.class) – tells JUnit to use Spring’s own built-in Test Runner that is aware of Spring-specific annotations
  • @ContextConfiguration(locations={“classpath:/config/applicationContext.xml”}) – creates a Spring context for this test from the specified context file.
  • @Transactional – before the method is executed a transaction is opened, upon exit the transaction is commited unless an exception occurs in which case it is rolled back.
  • @TransactionConfiguration(transactionManager=”transactionManager”, defaultRollback=false) – defines class-level metadata for configuring transactional tests
  • @Rollback(true) – forces the database to rollback this transaction when the test terminates regardless of whether the test fails, an exception is thrown or not.

Sample usage:

package com.bewareofthebear.test;

import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.bewareofthebear.domain.model.customer.Customer;
import com.bewareofthebear.infrastructure.persistence.CustomerDao;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/config/applicationContext.xml"})
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
@Transactional
public class JPATestCustomer {

  @Autowired
  CustomerDao customerDao;

  @Before
  public void setUpTestDataWithinTransaction() {
    Customer customer = null;

    customer = new Customer ();
    customer.setFirstName("John"); customer.setFirstName("Doe");
    customerDao.persist(customer);

    customer = new Customer ();
    customer.setFirstName("Jack"); customer.setFirstName("Rabbit");
    customerDao.persist(customer);

    customer = new Customer ();
    customer.setFirstName("Richard "); customer.setFirstName("Roe");
    customerDao.persist(customer);
  }

  @Test
  @Rollback(true)
  public void countTest() {
    Long count = customerDao.countAll();
    assertEquals(count, new Long(3));
  }

  @Test
  @Rollback(true)
  public void findAllTest() {
    List<Customer> all = customerDao.findAll();
    assertEquals(all.size(), 3);
  }

}

Complete project

complete sources used during writing.

Problem

Mapping One to One association with JPA is pretty straitforward. The following entities:

Employee.java

package com.application.onetoone.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

}

and

Desk.java

package com.application.onetoone.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "DESKS")
public class Desk {

  private Integer deskId;
  private String description;
  private Employee employee;

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  @Column(name="desk_id")
  public Integer getDeskId() {
    return deskId;
  }

  @Column(name="description")
  public String getDescription() {
    return description;
  }

  @OneToOne(mappedBy="desk", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setDeskId(Integer deskId) {
    this.deskId = deskId;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

generate the following database schema:
One-To-One
The owning entity – Employee – contains additional column – desk_id, which maps to the ownded entity – Desk. Having the configuration above I can:

assign a desk to employee:

    Employee employee = new Employee ();

    desk = deskDao.load(2);
    if (null == desk.getEmployee()) {
      employee.setDesk(desk);
    }

ask where a given employee is sitting:

    String description = employee.getDesk().getDescription();

or ask who is sitting at given desk:

    Integer employeeId = desk.getEmployee().getEmployeeId();

and, since they are not glued, an employee can empty his current desk and get a new one:

    Employee employee = new Employee ();

    desk = deskDao.load(4);
    if (null == desk.getEmployee()) {
      employee.setDesk(desk);
    }

A relation between employee and a seat is temporal. An employee can leave, a seat can be freed and reassing to someone else. There are however relations, which are everlasting and can not be changed over the time of entity life. Let’s say, I want to identify an employee – I give him a first name, last name and a date of birth. In order to to this I crete a separate entity:

EmployeeDetails.java

package com.application.onetoone.model;

import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "EMPLOYEE_DETAILS")
public class EmployeeDetails {

  private Integer employeeId;
  private String firstName;
  private String lastName;
  private Calendar dateOfBirth;
  private Employee employee;

  @Id
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @Column(name="first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name="last_name")
  public String getLastName() {
    return lastName;
  }

  @Temporal(TemporalType.DATE)
  @Column(name="date_of_birth")
  public Calendar getDateOfBirth() {
    return dateOfBirth;
  }

  @OneToOne(mappedBy="employeeDetails", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  public void setDateOfBirth(Calendar dateOfBirth) {
    this.dateOfBirth = dateOfBirth;
  }

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

which I want to share a primary key with it’s parent entity – employee, which I need to amend:

package com.application.onetoone.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;
  private EmployeeDetails employeeDetails;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @PrimaryKeyJoinColumn
  public EmployeeDetails getEmployeeDetails() {
    return employeeDetails;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

  public void setEmployeeDetails(EmployeeDetails employeeDetails) {
    this.employeeDetails = employeeDetails;
  }

}

The entities above generate the following database schema:
One-To-One
As you can see, there is no foreign key generated between Employee and EmployeDetails. This is default Hibernate behaviour and was already raised as a bug, however, it is not fixed as of now. Additionally, the configuration above does not assign any key to EmployeeDetails and the following code:

    Employee employee = new Employee ();

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John");
    employeeDetails.setLastName("Doe");
    employee.setEmployeeDetails(employeeDetails);      

    employeeDao.save(employee);

throws a HibernateSystemException with a following message: ids for this class must be manually assigned before calling save()
.
How do I assign it ?

Solution one – simple and ugly

Without going beyond the JPA annotation there is nothing smart that can be done. employee_id is not known before the Employee record is actually persisted, so it seems that the only way to get it right is:

    Employee employee = new Employee ();

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    // save it
    employee = employeeDao.save(employee); 

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John");
    employeeDetails.setLastName("Doe");
    // set employee_id
    employeeDetails.setEmployeeId(employee.getEmployeeId());
    employee.setEmployeeDetails(employeeDetails);      

    // save again
    employeeDao.save(employee);

The part of the code responsilbe for a proper mapping is left to the client. Obviously, it does the job, but this looks like workaround rather than solution.

Solution two – Hibernate GenericGenerator

JPA does not provide support for custom key generators, but Hibernate is closing the gap. First, we have to tell Hibernate, where the source of the primary key. Hibernate @GenericGenerator annotations provides the solution:

EmployeeDetails.java

package com.application.onetoone.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

@Entity
@Table(name = "EMPLOYEE_DETAILS")
public class EmployeeDetails {

  private Integer employeeId;
  private String firstName;
  private String lastName;
  private Date dateOfBirth;
  private Employee employee;

  @Id
  @GeneratedValue(generator = "foreign")
  @GenericGenerator(name = "foreign", strategy = "foreign",
    parameters = {@Parameter(name = "property", value = "employee")}
  )
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @Column(name="first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name="last_name")
  public String getLastName() {
    return lastName;
  }

  @Temporal(TemporalType.DATE)
  @Column(name="date_of_birth")
  public Date getDateOfBirth() {
    return dateOfBirth;
  }

  @OneToOne(mappedBy="employeeDetails", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public void setDateOfBirth(Date dateOfBirth) {
    this.dateOfBirth = dateOfBirth;
  }

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

@GenericGenerator with strategy set to foreign tells Hibernate to obtain a primary key value from one of its properties.
The last thing left to do is to make sure, the property used to generate id value is not null. Since EmployeeDetails is an owned entity, it makes perfect sense to change the the owning entity – Employee:

package com.application.onetoone.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;
  private EmployeeDetails employeeDetails;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @PrimaryKeyJoinColumn
  public EmployeeDetails getEmployeeDetails() {
    return employeeDetails;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

  public void setEmployeeDetails(EmployeeDetails employeeDetails) {
    this.employeeDetails = employeeDetails;
    // for @GenericGenerator
    employeeDetails.setEmployee(this);
  }

}

Now we can create an employee:

    Employee employee = new Employee ();      

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John"); employeeDetails.setLastName("Doe");
    employee.setEmployeeDetails(employeeDetails);

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    employeeDao.save(employee);

Ask where employee is sitting:

    String desk = employeeDetailsDao.find("lastName", "Doe").get(0).getEmployee().getDesk().getDescription();

or who is sitting here:

    String employeeName = deskDao.find("description", "T114A45").get(0).getEmployee().getEmployeeDetails().getLastName();

Problem

Most likely, each DAO class implements basic CRUD operations. Since these are the same for all possible entities, it makes perfect sense to have one generic DAO class which implements common CRUDs and extend it when necessary adding anything which is required by a specific entity instance.

Simple generic DAO should implement the following interface:

SimpleDao.java

package com.application.sample;

import java.io.Serializable;

public interface SimpleDao <PK extends Serializable, E extends AbstractEntity <PK>> {
  public E create(E entity);
  public E read(PK id);
  public E update(E entity);
  public void delete(PK id);
}

When implementing generic DAO using Hibernate as JPA provider we are facing the problem. Hibernate load and get operations require information about object being retrieved. Either object class, class name or the object instance has to be provided before actual read operation can be executed. None of these information is accessible before generic DAO is extended and instantiated. I can think of two possible solutions, an obvious one – asking implementation to deliver required information, and nice one – using java.lang.reflection.ParameterizedType.

Lets assume, I have an abstract entity, which defines fields common to all entities in my application:

AbstractEntity .java

package com.application.sample;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.persistence.Version;

@MappedSuperclass
public abstract class AbstractEntity <PK extends Serializable> implements Serializable {
  private static final long serialVersionUID = -6991226731574845156L;

  public AbstractEntity () {
  }

  private PK primaryKey;
  private Integer version;

  @Transient
  public PK getPrimaryKey () {
    return primaryKey;
  }

  @Version
  @Column(name="version", nullable=false)
  public Integer getVersion() {
    return version;
  }

  public void setPrimaryKey (PK primaryKey) {
    this.primaryKey = primaryKey;
  }

  public void setVersion(Integer version) {
    this.version = version;
  }

}

And Employee entity:

Employee.java

package com.application.sample;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Index;

@Entity
@Table(name = "EMPLOYEES")
@org.hibernate.annotations.Table(
  indexes = {@Index(name="lastNameIdx", columnNames={"last_name"} )},
  appliesTo = "EMPLOYEES"
)
public class Employee extends AbstractEntity <Integer> {
  private static final long serialVersionUID = -1693502528557287614L;

  private String firstName = null;
  private String lastName = null;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return getPrimaryKey();
  }

  @Column(name = "first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name = "last_name")
  public String getLastName() {
    return lastName;
  }

  public void setEmployeeId(Integer employeeId) {
    setPrimaryKey(employeeId);
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

}

For an Employee entity I want to have a possibility to find employees by their last name, so I add the required method the specification:

EmployeeDao.java

package com.application.sample;

import java.util.List;

public interface EmployeeDao extends SimpleDao<Integer, Employee> {

  public List<Employee> readEmployeeByLastName(String lastName);

}


Solution one – pass the buck.

Generic DAO implementation:

AbstractPassTheBuckDao.java

package com.application.sample;

import java.io.Serializable;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractPassTheBuckDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  public AbstractPassTheBuckDao() {
  }

  protected AbstractPassTheBuckDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // Ask the implementation for entity class
  abstract protected Class<E> getPersistentClass ();

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeePassTheBuckDao.java

package com.application.sample;

import java.util.List;

public class EmployeePassTheBuckDao
       extends AbstractPassTheBuckDao <Integer, Employee> implements EmployeeDao {

  // Deliver entity class
  protected Class<Employee> getPersistentClass() {
    return Employee.class;
  }

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}


Solution two – do it yourself.

Generic DAO implementation:

AbstractDoItYourselfDao.java

package com.application.sample;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractDoItYourselfDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  private Class<E> persistentClass;

  @SuppressWarnings("unchecked")
  public AbstractDoItYourselfDao() {
    // retrieve entity class
    ParameterizedType paramType = null;
    Type type = getClass().getGenericSuperclass();
    if (type instanceof ParameterizedType) {
      paramType = (ParameterizedType) type;
    } else {
      paramType = (ParameterizedType) getClass().getSuperclass().getGenericSuperclass();
    }

    if (paramType.getActualTypeArguments().length == 2) {
      if (paramType.getActualTypeArguments()[1] instanceof TypeVariable) {
        throw new IllegalArgumentException("Could not persistent entity class using reflection");
      } else {
        persistentClass = (Class<E>) paramType.getActualTypeArguments()[1];
      }
    } else {
      persistentClass = (Class<E>) paramType.getActualTypeArguments()[0];
    }

  }

  protected AbstractDoItYourselfDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // deliver entity class
  protected Class<E> getPersistentClass () {
    return persistentClass;
  }

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeeDoItYourselfDao .java

package com.application.sample;

import java.util.List;

public class EmployeeDoItYourselfDao
       extends AbstractDoItYourselfDao <Integer, Employee> implements EmployeeDao {

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}

As seen above, the EmployeeDao implementation implements only Employee related operations and there is no need to deliver Employee class to generic DAO superclass.


© 2007 Toss a coin | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress