Personal Finance Manager Persistance (Part Four)

Content

Account Owner Persistance

Summary

So far we have completed the basic UML model and maven project for the domain entity Owner, runUnit tests and generated some HTML quality metrics. We also installed the pfm-model project to our local maven repository in '.m2/repositories'. We can now refer to this project from our persistance layer which we will create next.

Creating the Database Layer Project (Epic 1a Task iii)

The database layer project will deal only with accessing a database and providing CRUD operations for our pfm-model entities. Therefore keeping this code separate makes it easier to maintain, doing it this way is called 'Separation of Concerns'. If we want to provide different database support or make improvements to the database access we only need to check out this project and make changes. This diagram shows our two projects and how they relate to each other.

artifacts.png

To create the new database project (known as persistance) execute this maven command.

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.inivitiv.tutorials.pfm.persist -DartifactId=pfm-persistence -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

You now have a new maven project called pfm-persistence, lets now add it to git version control as part of our good coding practice.

Execute this git command...

git init
Simple to do and so much advantage, really does not make sense not to do it.

The Hibernate Framework

For our persistance layer we will firstly add the Hibernate libraries to our pom.xml. Hibernate abstracts away all the necessary SQL commands required to save our Domain classes (Owner etc..) to a database of our choice. It also allows you to dynamically configure your database from the Java classes that you write by using Annotations (we used these in the pfm-model project originally). Go to the Hibernate Site to find out more, a good tutorial on Hiberate can be found at Hibernate Community Docs Tutorial. But you can learn here as well ;)

To start with we will use a pure Java in memory database and switch later once our unit tests are working. Update your pom.xml to match this version...
(click to select)

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.inivitiv.tutorials.pfm.persist</groupId>
  <artifactId>pfm-persistence</artifactId>
  <version>1.0-1</version>
  <packaging>jar</packaging>

  <name>pfm-persistence</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <hsql.version>2.3.2</hsql.version>
    <hibernate.version>3.5.4-Final</hibernate.version>
  </properties>

  <dependencies>
    <dependency>
        <groupId>com.inivitiv.tutorials.pfm.domain</groupId>
        <artifactId>pfm-model</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hsqldb</groupId>
        <artifactId>hsqldb</artifactId>
        <version>{hsql.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>{hibernate.version}</version>
    </dependency>
    <!-- Hibernate annotations for annotated classes -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>{hibernate.version}</version>
    </dependency>    
    <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-simple</artifactId>
       <version>1.5.8</version>
    </dependency>
    <!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
    <dependency>
       <groupId>javassist</groupId>
       <artifactId>javassist</artifactId>
       <version>3.12.1.GA</version>
    </dependency>
  </dependencies>
  <build>
  <plugins>
      <!-- Must compile to JDK 1.6 to use Generics -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <verbose>true</verbose>
          <fork>true</fork>
          <compilerVersion>1.6</compilerVersion>
        </configuration>
      </plugin>
      <!-- Allows for dynamic database table creation etc..-->
      <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>hibernate3-maven-plugin</artifactId>
          <version>2.0</version>
          <configuration>
              <components>
                  <component>
                      <name>hbm2ddl</name>
                      <implementation>annotationconfiguration</implementation>
                  </component>
               </components>
          </configuration>
          <!-- Database used during testing -->
          <dependencies>
               <dependency>
                   <groupId>hsqldb</groupId>
                   <artifactId>hsqldb</artifactId>
                   <version>{hsql.version}</version>
               </dependency>
          </dependencies>
      </plugin>
  </plugins>
  </build>
</project>

We now have the dependencies needed to create the persistance layer and are able to run Unit tests against the created data access objects (DAO).

The Owner Data Access Object (Epic 1a, Stories i, ii, iii, iv)

For each entity in the pfm-model project we will eventually create DAO's to take care of the CRUD operations. Each DAO should be coded against an interface, so we would have an interface for each DAO. However we can box clever at this stage and use Java Generics (JDK1.6) and only have to define one interface that all the DAO's will implement. Using interfaces also helps later when we move to the Spring framework (explained later).

This class diagram illustrates what we are going to create and the dependencies for our pfm-persistance project.

pfm-persist-datamodel.png

It can be seen from this diagram that pfm-persistance has dependencies to the pfm-model project and to the Hibernate framework. The Java Generics interface is named 'PFMGenericDao' and its Owner implementation is named 'OwnerDaoImpl'.

Create the PFMGenericDao interface in the com.inivitiv.tutorials.pfm.persist package. (click to select)

package com.inivitiv.tutorials.pfm.persist;

import java.io.Serializable;
import java.util.List;
/**
 * PFM Generic Data Access Object interface.
 * @author petersnr
 *
 * @param  - Dao Class extending this interface.
 * @param  - Dao Class must have an ID that is Serializable.
 */
public interface PFMGenericDao  < T, ID extends Serializable >{
    /**
     * Save method for a domain class.
     * @param entity - The domain entity to save.
     * @return - Returns the ID of the saved entity
     */
    Long saveOrUpdate( T entity);
    /**
     * Delete method for a domain class.
     * @param entity - The entity to delete from the db.
     */
    void delete( T entity);
    /**
     * Find an entity by its primary key.
     * @param id - The primary key id.
     * @return - The entity with the requested id.
     */
    T findByPrimaryKey( ID id);
    /**
     * Find all entities of type T.
     * @return - A List containing all the entities.
     */
    List< T > findAll();
}

This interface defines all the CRUD operations for the pfm-model domain classes.

  • Create & Update, 'Long saveOrUpdate(T entity);'
  • Read, 'T findByPrimaryKey(ID id);'
  • Delete, 'void delete(T entity);'
  • A utility method to list all 'List findAll();'

The Owner DAO Implementation

Now we define an implementation of the above interface for the Owner entity with a JUnit test as well. First the implementation... (click to select)

package com.inivitiv.tutorials.pfm.persist;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import com.inivitiv.tutorials.pfm.domain.Owner;

/**
 * Owner Hibernate OwnerDao implementation.
 * @author petersnr
 *
 */
public class OwnerDaoImpl implements PFMGenericDao {
    /**
     * The Hibernate session.
     */
    private SessionFactory sessionFactory;

    /**
     * Default no arg constructor (for IoC Spring)
     */
    public OwnerDaoImpl(){
	
    }
    public OwnerDaoImpl(SessionFactory sessionFactory){
      this.sessionFactory = sessionFactory;
    }
    /**
     * Finds all Owner instances from the database.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public List findAll() {
      
      sessionFactory.getCurrentSession().beginTransaction();
      List owners = sessionFactory.getCurrentSession().createQuery("from Owner").list();
      sessionFactory.getCurrentSession().getTransaction().commit();
      return owners;
    }
    /**
     * Find Owner instance by primary key.
     * @param id - The primary key of the instance to load.
     */
    @Override
    public Owner findByPrimaryKey(Long id) {
	
      sessionFactory.getCurrentSession().beginTransaction();
      Object o = sessionFactory.getCurrentSession().get(Owner.class, id);
      sessionFactory.getCurrentSession().getTransaction().commit();
      return (Owner)o;
    }

    
    /**
     * Save a Owner instance to the database.
     */
    @Override
    public final Long saveOrUpdate(final Owner owner) {
    	
      sessionFactory.getCurrentSession().beginTransaction();
      sessionFactory.getCurrentSession().saveOrUpdate(owner);
      sessionFactory.getCurrentSession().getTransaction().commit();
      return owner.getId();
    }
    /**
     * Delete an Owner from the database.
     */
    @Override
    public void delete(Owner entity) {
	
    	sessionFactory.getCurrentSession().beginTransaction();
    	sessionFactory.getCurrentSession().delete(entity);
    	sessionFactory.getCurrentSession().getTransaction().commit();
    }
    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

Unit Testing OwnerDAO

In order to Unit test the OwnerDaoImpl the hibernate framework requires a session manager to be set up that reads in a configuration file name 'hibernate.cfg.xml' that must define the database and the mapping classes. Its possible to define within this configuration file a mapping file or use annotations. The pfm-model classes use annotation so this will be the method employed. The pom.xml for the pfm-persistance project defines a dependency on the HSQL database that we will use for testing purposes only. It also has an entry in the <build> section under <plugins> that defines the hibernate actions to create the tables from the annotations. For this to work we must make some declarations within the hibernate.cfg.xml configuration file.

This diagram shows the necessary declarations and how they relate to each other... hibernate-howto.png

Create the hibernate.cfg.xml file within the 'src/test/resources' directory as listed below. (click to select)

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE hibernate-configuration PUBLIC 
   "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
   "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 
<hibernate-configuration>  
<session-factory>   
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>   
<property name="hibernate.connection.url">jdbc:hsqldb:data/pfm;shutdown=true</property>   
<property name="hibernate.connection.username">sa</property><!-- default username -->   
<property name="hibernate.connection.password"></property><!-- default password -->
<property name="hibernate.connection.pool_size">1</property>   
<property name="hibernate.connection.autocommit">true</property>       	
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>   
<!-- creates the tables from the entites automatically -->   
<property name="hibernate.hbm2ddl.auto">create</property>
<property name="show_sql">true</property>   
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>   
<mapping class="com.inivitiv.tutorials.pfm.domain.Owner"/>  
</session-factory> 
</hibernate-configuration> 

In summary, these steps are necessary for the hibernate framework to function correctly.

  1. Annotate the database entity classes using the @Entity annotation from the javax.persistence package (see the Owner class in the pfm-model project).
  2. In the pfm-persistance project declare within the pom.xml the hibernate dependencies and the hibernate3-maven-plugin section.
  3. Create a 'hibernate.cfg.xml' file with the database connection information and the class that will be used for the mapping from class to database tables.
  4. Create an interface to define the database CRUD operations (not strictly necessary but is good programming practice).
  5. Create a Data Access Class (DAO) that implements the interface, this object will access the hibernate Session for the CRUD operations.
  6. Create an application or unit test that creates an instance of the SessionManager, this will read in the hibernate.cfg.xml file to set up the Sessions that will be used by the DAO for the CRUD operations.

The OwnerDao JUnit Test

Finally we are ready to try and test our OwnerDao CRUD operations on a test database (HSQL). Add this JUnit 4 test to the 'src/test/java' folder in the 'com.inivitiv.tutorials.pfm.domain' package. (click to select)

package com.inivitiv.tutorials.pfm.domain;

import static org.junit.Assert.*;

import java.util.List;


import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.inivitiv.tutorials.pfm.persist.OwnerDaoImpl;

/**
 * JUnit Tests for the UserDAO
 */
public class OwnerDaoImplTest {

    private static OwnerDaoImpl ownerDaoImpl;
    private static SessionFactory sessionFactory;
    
  @BeforeClass
  public static void globalSetup(){
      sessionFactory = buildSessionFactory();
      try {
      sessionFactory.openSession();
      ownerDaoImpl = new OwnerDaoImpl(sessionFactory);
      }
      catch(HibernateException hbe){
	  hbe.printStackTrace();
      }
      
  }
  @AfterClass
  public static void cleanUp() {
      List all = ownerDaoImpl.findAll();
      Owner owner = null;
      for(Object obj: all){
	owner = (Owner)obj;
	ownerDaoImpl.delete(owner);
      }
      sessionFactory.close();
  }
  private static SessionFactory buildSessionFactory() {
      try {
          // This will read in the hibernate.cfg.xml file from the classpath
          // and set up the database by reading annotations from the classes 
          // defined within the configuration file.
          return new AnnotationConfiguration().configure().buildSessionFactory();
      }
      catch (Throwable ex) {
          ex.printStackTrace();
          throw new ExceptionInInitializerError(ex);
      }
  }
    
  @Test
  public void testFindAll() {
      Owner owner = new Owner("Peter", "Terence", "Goudman");
      ownerDaoImpl.saveOrUpdate(owner);
      owner = new Owner("Lisa", "Marie", "James");
      ownerDaoImpl.saveOrUpdate(owner);
      
      List all = ownerDaoImpl.findAll();
      assertTrue(all.size() >= 2);
  }

  @Test
  public void testFindByPrimaryKey() {
	Owner owner = new Owner("Peter", "Terence", "Goudman");
	Long id = ownerDaoImpl.saveOrUpdate(owner);
	Owner foundOwner = ownerDaoImpl.findByPrimaryKey(id);
	assertNotNull(foundOwner);
	//assertEquals(id, foundOwner.getId());
  }

    @Test
    public void testSave() {
	Owner owner = new Owner("Peter", "Terence", "Goudman");
	Long id = ownerDaoImpl.saveOrUpdate(owner);
	assertTrue(id.intValue() > 0);
    }
  //@Test(expected=org.hibernate.ObjectNotFoundException.class)
  public void testDelete() {
      Owner owner = new Owner("Peter", "Terence", "Goudman");
      ownerDaoImpl.saveOrUpdate(owner);
      Long id = owner.getId();
      
      ownerDaoImpl.delete(owner);
      owner = ownerDaoImpl.findByPrimaryKey(id);
      System.out.println(owner);
      //assertNull(owner);
  }
}

Now run the unit test by executing this command...

mvn test

Sprint One is not actually fully completed as the acceptance tests for the User Storys are not fully implemented. However, this is enough work for this Sprint so we will define a new Sprint and create a web application to fully realize our Owner entities and complete the acceptance tests. You may if you wish add checkstyle and emma to the pfm-persistance pom.xml as you did in the pfm-model project.

Finally commit all your changes to this project in git and tag the release to match your pom.xml version tag. Issue these final commands for Sprint One.

git commit -a -m "Sprint One Completed, ready for tagging"

git tag -a 1.0.1 -m "Initial release with Owner entity and tests" master

mvn clean install
Now you have the pfm-persitance project commited and tagged with the matching version number of your pom.xml. It is now also installed within your local maven repository in '<USER HOME/>.m2/repository'. Matching the model and the persistance layers version numbers and tags makes it easy to checkout the correct matching versions that work together.

We will now take a look at our work backlog and define what to tackle in Sprint Two, click the next arrow to continue...

Date Entered2014-06-09