Home > Articles > Programming > Java

Java Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Integrating HSQLDB with Spring and Hibernate

Last updated Mar 14, 2003.

While SQL executed through JDBC may solve all of your database needs for a client application, as your application becomes more robust you may find yourself in need of a more advanced architecture. In this article I review two of the most popular frameworks for building enterprise applications in the context of building a client application: Hibernate and Spring.

Hibernate is an object-relation mapping (ORM) library that attempts to mitigate the disconnect between an object-oriented application and a relational database. In short, when you model objects and object relationships, including inheritance and polymorphism, those concepts do not map very well to relational databases. But, relational databases have survived the test of time and have proven to be the best way to manage a reasonable amount of data (I'll save the discussions about non-relational databases for large datasets for another discussion.) Hibernate allows you to define your objects and the relationships between your objects in an object-oriented fashion and then, through either annotations or XML configuration, Hibernate will persist your objects to and from a relational database.

Spring is a robust enterprise framework that does many things, but for the purposes of this article, it allows you to cleanly implement business processes (services) and automatically wire application components together using the inversion of control (IoC) and dependency injection (DI) design patterns. In this article we'll be developing Spring services that model business behavior and Spring repositories that implement data access logic against Hibernate annotated model classes.

Listing 1 starts this process by reviewing the Customer Hibernate object. We're going to continue with the same simple data model created in the previous section that contains a single table: CUSTOMER.

Listing 1. Customer.java

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.geekcap.gtd.model;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 *
 * @author shaines
 */
@Entity
@Table( name = "CUSTOMER" )
public class Customer implements Serializable
{
    /**
     * Serialization id
     */
    private static final long serialVersionUID = 1L;

    /**
     * The Customer's id attribute
     */
    @Id
    @GeneratedValue( strategy = GenerationType.AUTO )
    @Column( name = "ID" )
    private long id;

    @Column( name = "FIRSTNAME" )
    private String firstName;

    @Column( name = "LASTNAME" )
    private String lastName;

    @Column( name = "EMAIL" )
    private String email;

    @Column( name = "PASSWORD" )
    private String password;


    public Customer()
    {
    }

    public Customer( String firstName,
                     String lastName,
                     String email,
                     String password )
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.password = password;
    }

    public String getEmail()
    {
        return email;
    }

    public void setEmail( String email )
    {
        this.email = email;
    }

    public String getFirstName()
    {
        return firstName;
    }

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

    public long getId()
    {
        return id;
    }

    public void setId( long id )
    {
        this.id = id;
    }

    public String getLastName()
    {
        return lastName;
    }

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

    public String getPassword()
    {
        return password;
    }

    public void setPassword( String password )
    {
        this.password = password;
    }
}

The Customer class looks a lot like a plain old java object (POJO), but with a few additional annotations. The annotations are defined as follows:

  • Entity: identifies the customer class as an entity, which is part of the Java Persistence API (JPA) standard and means that this object is a persistent entity that can be mapped to a relational database table or tables.
  • Table: identifies the table to which this entity maps, which is the CUSTOMER table in this example.
  • Column: each field in the Customer class is mapped to a named column; the Column annotation identifies the column to which each field maps.
  • Id: identifies that the id field is the primary key of the CUSTOMER table.
  • GeneratedValue: tells Hibernate that the id field maps to an automatically generated (auto-incrementing) database column.

Listing 2 and 3 show the interface that defines the data persistence methods and the class that implements that interface and performs the actual persistence.

Listing 2. CustomerDao.java

package com.geekcap.gtd.dao;

import com.geekcap.gtd.model.Customer;
import java.util.List;

/**
 * Defines the data access methods for Customer persistence
 *
 * @author shaines
 */
public interface CustomerDao
{
    public Customer findById( long id );
    public List<Customer> findAll();
    public void save( Customer customer );
    public void update( Customer customer );
    public void delete( Customer customer );
    public void shutdown();
}

Listing 3. CustomerDaoImpl.java

package com.geekcap.gtd.dao;

import com.geekcap.gtd.model.Customer;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Implements the data access methods for Customer persistence
 *
 * @author shaines
 */

@Repository( "customerDao" )
public class CustomerDaoImpl extends HibernateDaoSupport implements CustomerDao
{
    @Override
    public Customer findById( long id )
    {
        return ( Customer )getHibernateTemplate().get( Customer.class, id );
    }

    @Override
    public List<Customer> findAll()
    {
        return getHibernateTemplate().find( "from com.geekcap.gtd.model.Customer" );
    }

    @Override
    public void save( Customer customer )
    {
        getHibernateTemplate().save( customer );
    }

    @Override
    public void update( Customer customer )
    {
        getHibernateTemplate().update( customer );
    }

    @Override
    public void delete( Customer customer )
    {
        getHibernateTemplate().delete( customer );
    }

    @Override
    public void shutdown()
    {
        getHibernateTemplate().getSessionFactory().openSession().createSQLQuery( "SHUTDOWN" ).executeUpdate();
    }

    @Autowired
    public void init( SessionFactory sessionFactory )
    {
        setSessionFactory( sessionFactory );
    }
}

The CustomerDaoImpl class extends Spring's HibernateDaoSupport class. This class contains an object called the HibernateTemplate that implements the template design pattern to perform Hibernate queries. The template design pattern implements all of the boiler plate code inside the template and then delegates to your class to perform the custom steps. The Hibernate template handles managing the SessionFactory, creating a session, executing whatever you want it to, and then cleaning itself up. By extending HibernateDaoSupport, we only need to define a method that is autowired to accept a SessionFactory (because HibernateDaoSupport's setSessionFactory() method is final and cannot be automatically wired) and then access the underlying HiberateTemplate by calling the getHibernateTemplate() method. The methods we invoke are:

  • get(): given the primary key of an object (identified by the @Id annotation), returns the object with that primary key
  • find(): executes a Hibernate Query Language (HQL) expression and returns a list of the requested objects
  • save(): writes an object to the database
  • update(): updates, or replaces, an object in the database with the new object, determined by primary key
  • delete(): deletes an object from the database

One additional method that may present some confusion is the shutdown() method. This method executes an HSQLDB SHUTDOWN command, similar to how we did it manually in the previous section via an executeQuery(). A challenge that I discovered when writing this article is that adding “shutdown=true” to the JDBC URL did not force data written to HSQLDB to be persisted on shutdown. My suspicion is that closing the application without telling Hibernate is causing the application to exit without closing all of its connections (and hence the "shutdown=true" parameter is not effective.) I could be wrong, and if you have any insight, please email me at steve@javasrc.com and I'll update the article. But regardless, invoking the shutdown() method does force data to be persisted properly.

Listings 4 and 5 show the business layer: listing 4 shows the service interface and listing 5 shows the implementation of that service interface.

Listing 4. CustomerService.java

package com.geekcap.gtd.service;

import com.geekcap.gtd.model.Customer;
import java.util.List;

/**
 * Defines the business methods for the customer service
 *
 * @author shaines
 */
public interface CustomerService
{
    public Customer findById( long id );
    public List<Customer> findAll();
    public void save( Customer customer );
    public void update( Customer customer );
    public void delete( Customer customer );
    public void shutdown();
}

Listing 5. CustomerServiceImpl.java

package com.geekcap.gtd.service;

import com.geekcap.gtd.dao.CustomerDao;
import com.geekcap.gtd.model.Customer;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Implements the business methods for the customer service
 * @author shaines
 */
@Service( "customerService" )
public class CustomerServiceImpl implements CustomerService
{
    @Autowired
    private CustomerDao customerDao;

    @Override
    public Customer findById( long id )
    {
        return customerDao.findById( id );
    }

    @Override
    public List<Customer> findAll()
    {
        return customerDao.findAll();
    }

    @Override
    public void save( Customer customer )
    {
        customerDao.save( customer );
    }

    @Override
    public void update( Customer customer )
    {
        customerDao.update( customer );
    }

    @Override
    public void delete( Customer customer )
    {
        customerDao.delete( customer );
    }

    @Override
    public void shutdown()
    {
        customerDao.shutdown();
    }
}

The CustomerService adds questionable value to this application because it is simply a pass-through to the data access layer, but it is added for two reasons:

  1. It provides a layer of abstraction between the application and the data access layer
  2. It provides a place to add additional real business logic or business rules

Some may argue that it would be better for an application to talk directly to the data access layer, but I will typically disagree with this idea because it invariably leads to a corruption in the separation of concerns that we aim to achieve in manageable applications. In this example, the customer service does not add tangible business value, but consider processing that you might want to do on your data. For example, consider an application that wants to view all customer data in upper case letters. If you wanted to do this and did not have a business tier, where would you put that logic? If you put it in your application code then it would have to be rewritten everywhere you wanted this data. If you put it in the data access object, then it is responsible for more than just data access, which violates its concern. But if you were okay with putting that logic into the data access object, it would work fine until a new requirements for the original data arose. Now your data access object is becoming more and more complicated because it is coupling business logic with data access. There are other examples where this fall apart further, but in short, if you like separation of concerns then accessing your data access objects through a service layer is a good idea.

The CustomerServiceImpl class auto-wires in the data access object and then delegates its methods to calls against that data access object.

Listing 6 shows a sample application that performs HSQLDB data access through the CustomerService and CustomerDao objects.

Listing 6. SpringHibernateExample.java

package com.geekcap.gtd;

import com.geekcap.gtd.model.Customer;
import com.geekcap.gtd.service.CustomerService;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Sample application that demonstrates how to build an application context from
 * an XML file in the CLASSPATH and then access its beans.
 *
 * @author shaines
 */
public class SpringHibernateExample
{
    private static ApplicationContext applicationContext;
    private static CustomerService customerService;

    public static void showCustomers()
    {
        List<Customer> customers = customerService.findAll();
        System.out.println( "Customers:" );
        for( Customer customer : customers )
        {
            System.out.println( "\t" + customer.getEmail() );
        }
    }

    public static void addUser( String firstName, String lastName, String email, String password )
    {
        customerService.save( new Customer( firstName, lastName, email, password ) );
    }

    public static void shutdown()
    {
        customerService.shutdown();
    }

    public static void main( String[] args )
    {
        // Load the application context
        applicationContext = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" );

        // Load our customer service bean
        customerService = ( CustomerService )applicationContext.getBean( "customerService" );

        // Test code
        showCustomers();
        addUser( "Michael", "Haines", "michael@javasrc.com", "funkey" );
        showCustomers();
        shutdown();
    }
}

The main() method in listing 6 creates a new Spring ApplicationContext by locating the applicationContext.xml file in the CLASSPATH (meaning that it should be in the root of the resultant JAR file.) It then obtains access to the CustomerService bean with the name "customerService". The helper methods all invoke functionality on the CustomerService object, including the call to shutdown() to tell HSQLDB to persist its data back to its data files.

This example would not be complete without reviewing the applicationContext.xml and pom.xml files. The applicationContext.xml file, shown in listing 7 shows the glue that ties all of these components together and pom.xml, shown in listing 8, shows how all of the dependencies are resolved.

Listing 7. applicationContext.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:context="http://www.springframework.org/schema/context"
       xmlns:oxm="http://www.springframework.org/schema/oxm"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <!-- Component scan to find all Spring components -->
    <context:component-scan base-package="com.geekcap.gtd" />

    <!-- Data Source -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:file:testdb;shutdown=true" />
        <property name="username" value="SA" />
        <property name="password" value="" />
        <property name="initialSize" value="1" />
        <property name="maxActive" value="5" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxOpenPreparedStatements" value="10" />
    </bean>

    <!-- Hibernate Session Factory  -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />

        <!-- Hibernate configuration -->
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
            </props>
        </property>

        <!-- The packages that contain our Hibernate model classes -->
        <property name="packagesToScan">
            <list>
                <value>com.geekcap.gtd.model</value>
            </list>
        </property>
    </bean>

    <!-- Hibernate transaction management -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
</beans>

The first thing that the applicationContext.xml file does is to instruct Spring to scan the com.geekcap.gtd package for components. This will cause Spring to automatically discover all classes annotated with @Service and @Repository (as well as @Component and @Controller, but we'll save those for another discussion) and add them to the ApplicationContext.

Next it creates a data source using the Apache Commons DBCP connection pool, configured to use the HSQLDB driver (org.hsqldb.jdbcDriver) and the URL that references the testdb script in the current working directory (jdbc:hsqldb:file:testdb;shutdown=true).

With a data source in hand, the next step is to create a Hibernate SessionFactory, which is actually a Spring representation of it that finds annotated model beans. The data source is injected into the session factory, Hibernate is configured to use Hypersonic, and then it is instructed to scan the com.geekcap.gtd.model package for annotated classes.

Finally, a transaction manager is created and wrapped around the session factory. This facilitates things like rollbacks.

Listing 8. pom.xml

<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.geekcap</groupId>
    <artifactId>gtd</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>gtd</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>3.0.5.RELEASE</spring.version>
        <java.version>1.6</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.geekcap.gtd.SpringHibernateExample</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        
        <!-- Hibernate Dependencies -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>3.3.2.GA</version>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-annotations</artifactId>
            <version>3.4.0.GA</version>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.8.0.GA</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.0.0</version>
        </dependency>

        <!-- Logging: Log4J -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.5.8</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.6</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

The POM file does several things:

  • It configures the build to use Java 1.6
  • It copies all dependencies to the target's lib directory
  • It creates a manifest file in the resultant JAR file that includes all of the dependencies in the CLASSPATH and then points to the com.geekcap.gtd.SpringHibernateExample as the main-class for the JAR file
  • It includes all of the relevant Spring, Hibernate, DBCP, logging, and Hypersonic dependencies

After building this project (mvn clean install), the only extra thing you'll need to do to make it work is copy the testdb.script file from the root of the project into the target directory. Then you can launch it as follows:

java -jar gtd-1.0-SNAPSHOT.jar

You can download the source code for this article here.

I'd recommend that you refer to my Managing Java Build Lifecycles with Maven for more information about this pom.xml file.