Chapter 13. Put a Spring in your Step: Hibernate with Spring

What Is Spring?

If you’ve been paying attention to Java programming over the last few years, you’ve probably heard of the Spring Framework. The Spring Framework is an Inversion of Control (IoC) container with a number of additional integrated features and modules. To some it is just an IoC container; to others it is an entire platform for application development. (We will expand on what is meant in the next section.)

Created by Rod Johnson in 2000, Spring rose to prominence as an alternative to Enterprise Java Beans (EJBs). Between 2000 and 2004, Sun Microsystems released a series of (somewhat terrible) specifications for EJBs. EJBs were difficult to understand, develop, and deploy without writing volumes of repetitive code and/or using proprietary tools to shield yourself from the underlying technology. Rod’s seminal text, Expert One-on-One J2EE Design and Development, published by WROX in 2002, introduced a large number of developers to the idea of replacing EJBs with a simple IoC container and a number of APIs that served to isolate your programs from the rougher edges in Sun’s APIs. Instead of interacting directly with the JDBC, JMS, or the JTA APIs, you could use templates and helpers within Spring. While Sun’s core enterprise APIs are still important to know, a side-effect of using Spring was to lessen the choke-hold Sun had on defining and proposing new libraries and applications. Instead of programming to JDBC or JNDI directly, you could write your program to operate within Spring’s container which was relatively agnostic about what underlying technology you might choose to implement persistence or messaging or whatever you were trying to do. Spring wasn’t the only project to try to break through Sun’s monopoly on setting standards in the Java space, but it has unarguably been the most successful.

You might write a few of your persistence layer objects to interact with JDBC directly, but you also might choose to implement a few using some custom proprietary code from Oracle, and you may write the rest using Hibernate. Spring enables this choice by providing an agnostic IoC container and a collection of useful abstractions. The discussion surrounding enterprise development no longer revolves around Sun and the various standards produced by the Java Community Process (JCP); instead, Spring has encouraged a proliferation of choice by providing a common “bus” to which most new projects can build. Look at Spring’s integration with web frameworks for an example: Wicket, Struts 2 (was WebWork), Stripes, GWT, and Tapestry all provide first-class Spring integration, and Spring’s documentation details integration with JavaServer Faces, Tapestry, and WebWork. Spring provides out-of-the-box integration for various Object Relational Mapping frameworks, including Hibernate, JDO, Oracle TopLink, iBatis SQL Maps, and JPA. Spring is the platform that enables a “freedom of choice” for implementation, and almost every new open source library or project must, in some way, integrate with the Spring Framework to become relevant to the large audience of developers who use it. The consensus among many is that while Sun might tell you that Java is a “platform,” it is really just a language—Spring is the real platform. After you read this chapter, you’ll have an idea of how Spring takes much of the work out of using Hibernate.

What is inversion of control?

There’s no single definition of IoC, but a central concept is Dependency Injection. From Wikipedia:

...a pattern in which responsibility for object creation and object linking is removed from [the objects] and transferred to a factory.

Spring, the lightweight container, assumes responsibility for wiring together a set of components, and injects dependencies either via JavaBean properties or constructor arguments. Using Spring you develop a series of simple pieces, and then you tell Spring how to wire them together to create a larger system. This comes in very handy if your architecture relies on reusable “components.” In this chapter, our Data Access Objects (DAOs) are implemented as components (or beans) in a Spring Application Context and our example classes depend on these components as bean properties. If we wanted to reuse our DAOs in another system such as a command-line utility or a web application, those systems would depend on the same components as bean properties, and Spring would take responsibility for wiring our components together based on an XML description of our program’s construction.

Spring encourages reusability by taking on the responsibility of gluing together a set of focused components. Your DAOs don’t care what they are connected to or what components are using them; they are focused solely on providing an interface to whatever component depends on them. If you are interested in a longer description of both Dependency Injection and Inversion of Control, you should read Martin Fowler’s Inversion of Control Containers and the Dependency Injection Pattern.

Combining Spring and Hibernate

Now that you know what Spring is all about, let’s get back to Hibernate. This chapter focuses on the DAO pattern, transaction abstractions, and helpers provided by the Spring Framework.

What can you expect from this chapter? While using Spring with Hibernate is straightforward, there are a few steps we need to take before we can really take advantage of Spring’s Hibernate integration. First, because Spring is an IoC container, we need to modify the examples from the previous chapters and create objects to “wire together.” We’ll be exploring the DAO pattern in the next section. After we’ve created one, we will modify our current set of examples to implement a common interface Test to which we will apply a Transactional annotation. Then we will create a Spring application context by writing an XML document which tells Spring what objects to create and what objects to wire together. Finally, we will write a command-line program that loads our Spring application context and starts an example program.

Note

Enough with this introduction. Let’s get to the code.

Adding the Spring framework as a project dependency

To use Spring in the example project, we’ll need to add another dependency to the build. Open up build.xml and add the dependency highlighted in bold in Example 13-1.

Example 13-1. Adding Spring as a dependency
<artifact:dependencies pathId="dependency.class.path">
  <dependency groupId="hsqldb" artifactId="hsqldb" version="1.8.0.7"/>
  <dependency groupId="org.hibernate" artifactId="hibernate" version="3.2.4.sp1">
    <exclusion groupId="javax.transaction" artifactId="jta" />
  </dependency>
  <dependency groupId="org.hibernate" artifactId="hibernate-tools" version="3.2.0.
beta9a" />
  <dependency groupId="org.hibernate" artifactId="hibernate-annotations" version="
3.3.0.ga" />
  <dependency groupId="org.hibernate" artifactId="hibernate-commons-annotations" v
ersion="3.3.0.ga" />
  <dependency groupId="org.apache.geronimo.specs" artifactId="geronimo-jta_1.1_spe
c" version="1.1" />
  <dependency groupId="log4j" artifactId="log4j" version="1.2.14" />
  <dependency groupId="org.springframework" artifactId="spring" version="2.5"/>
</artifact:dependencies>

Great—now when we run the Ant build, we’re going to notice that the Maven Ant task will download spring-2.5.jar from one of the Maven repositories.

Writing a Data Access Object

A Data Access Object (DAO) is a common pattern which helps to isolate your application’s code from the code that accesses and manipulates records in a database. In a larger architecture, a DAO provides a boundary between two separate architectural layers. We are introducing DAO objects in the context of the Spring Framework to give you a sense of how you can fit Hibernate into an overall system architecture, and we are introducing DAO objects because they represent a common pattern you will encounter in any real-world system that needs to interact with a database.

What is a Data Access Object?

I’m always surprised when an author writes a twenty page description of the DAO pattern, its advantages, and its disadvantages. If you read the Sun J2EE blueprint, you’ll read a thick document explaining DAO creation patterns using factories and how the DAO pattern fits into the larger approach to enterprise application development. We’re going to skip much of the formality and just sum up the DAO pattern in two simple bullet points. The DAO pattern:

  • Consolidates all persistence operations (create, read, update, delete) into a single interface usually organized by table or object type.

  • Provides one or more swappable implementations of this interface which can use any number of different persistence APIs and underlying storage media.

Or, if you are looking for an even more compact definition: “DAOs hide all the gory details of persistence behind an interface.”

When you use an Object/Relational Mapping (ORM) framework such as Hibernate, you are usually approaching the database as a set of objects. The examples in this book revolve around three objects: Artist, Album, and Track, and the DAO objects we are going to create mirror these three objects: ArtistDAO, AlbumDAO, and TrackDAO. Figure 13-1 shows a class diagram of the ArtistDAO we will be creating shortly.

Isolating application and persistence code with a Data Access Object
Figure 13-1. Isolating application and persistence code with a Data Access Object

In this diagram, your application’s logic is represented as the class YourClass. YourClass would have a reference to an ArtistDAO interface which provides four simple methods:

Artist persist(Artist artist)

Saves the state of an Artist object to the database. This method will insert a new row or update an existing row depending on the state of the artist parameter. The contract for this method is to check the id property of the artist parameter. If the id property is null, insert a new row into the ARTIST table, and if the id property is not null, update the matching row in the database. This method returns a persisted Artist object; in other words, if you pass this method an Artist object with a null id property, it will create a new row in the database, and return an Artist object with a non-null id property containing the identifier of the newly inserted row.

void delete(Artist artist)

Deletes the matching row from the database.

Artist uniqueByName(String name)

Returns a single Artist with a name property equal to the name parameter.

Artist getArtist(String name, boolean create)

Finds the Artist with a matching name property. If the create parameter is true, and a matching Artist cannot be found, this method will create and persist a new Artist with the supplied name parameter.

While YourClass is coded to the ArtistDAO interface, you are really invoking an implementation of the ArtistDAO interface—ArtistHibernateDAO. This implementation of ArtistDAO extends Spring’s HibernateDaoSupport class which contains all the necessary magic to make writing Hibernate code as painless as possible. Using this HibernateDaoSupport, you can avoid writing all of the exception handling, transaction management, and session management code that you’ve seen in the previous chapters. In fact, I think you’ll be surprised (and maybe a bit disappointed) at how easy it is to Hibernate in Spring.

Tip

This book uses the following naming convention. A DAO interface is defined in the com.oreilly.hh.dao package, and its class name is the name of the related object with “DAO” appended (for example, ArtistDAO). The Hibernate-specific implementation of this interface is named ArtistHibernateDAO and is placed in the com.oreilly.hh.dao.hibernate package.

Why would I want to use a DAO?

There are a few reasons, but first and foremost is increased flexibility. Since you are coding to an interface, you can easily swap in a new implementation if you need to use a different O/R mapping service or storage medium. In the introduction I mentioned that Spring provides an integration layer that can work with any number of object-relational mapping technologies, from iBatis SQL Maps to Oracle’s TopLink to Hibernate, but Spring also provides a rich set of abstractions that make executing SQL with JDBC straightforward. In most of the applications with which I’ve worked, Hibernate provided most of the persistence logic, but it didn’t solve every problem. There are times when you need to execute SQL directly, and Spring provides classes like JDBCTemplate to make this very easy. When you put a DAO interface between your application’s logic and the persistence layer, you’ve made it easier to swap in other implementations for a particular DAO class or method when the need arises.

This flexibility and isolation goes both ways—it is easier to replace both the implementation of a specific DAO class, and it is easier to reuse your persistence layer when you need to rewrite or upgrade your application logic. Take a situation many readers of this book find themselves in right now: your team has been maintaining a legacy system written in Struts 1.x, and you want to upgrade the application to Stripes or Struts 2. If the persistence code is tightly coupled to the web framework code, you will likely find it impossible to rewrite one without rewriting the other.

Following the DAO pattern might seem unnecessary for a simple application, but more often than not, you have a need to reuse persistence code across multiple projects. Creating a set of DAO objects might seem contrary to the agile orthodoxy, but it is an investment in complexity that tends to pay off over time. If your system is anything more than a simple “Hello World” example, you’ll probably want to take some time to separate your persistence logic from your application.

Writing the ArtistDAO interface

Without delay, let’s jump into writing the code for this example. The first thing we need to do is write the ArtistDAO interface. Create an interface in the com.oreilly.hh.dao package named ArtistDAO and put the code shown in Example 13-2 in this new interface.

Example 13-2. The ArtistDAO interface
package com.oreilly.hh.dao;

import com.oreilly.hh.data.Artist;

/**
 * Provides persistence operations for the Artist object
 */
public interface ArtistDAO {

    /**
     * Persist an Artist instance (create or update)
     * depending on the value of the id
     */
    public Artist persist(Artist artist);

    /**
     * Remove an Artist from the database
     */
    public void delete(Artist artist);

    /**
     * Return an Artist that matches the name argument
     */
    public Artist uniqueByName(String name);

    /**
     * Returns the matching Artist object.   If the 
     * create parameter is true, this method will
     * insert a new Artist and return the newly created
     * Artist object.
     */
    public Artist getArtist(String name, boolean create);
}

All right, that was relatively easy; all we’re talking about here is a few simple methods to implement. Let’s take a look at how we implement this logic using the HibernateDaoSupport class.

Implementing the ArtistDAO interface

The next step is to write an implementation for the ArtistDAO. We are going to write an ArtistHibernateDAO which implements the ArtistDAO interface and extends Spring’s HibernateDaoSupport class. HibernateDaoSupport provides access to your Hibernate Session object and it also provides access to a HibernateTemplate which can be used to simplify almost any operation you can accomplish with the Hibernate Session object. To implement ArtistDAO, create a new class in the com.oreilly.hh.dao.hibernate package to contain the Hibernate-specific implementation of ArtistDAO shown in Example 13-3.

Example 13-3. Implementing the ArtistDAO interface
package com.oreilly.hh.dao.hibernate;

import java.util.HashSet;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.oreilly.hh.dao.ArtistDAO;
import com.oreilly.hh.data.Artist;
import com.oreilly.hh.data.Track;

/**
 * Hibernate-specific implementation of the ArtistDAO interface. This class
 * extends the Spring-specific HibernateDaoSupport to provide access to 
 * the SessionFactory and the HibernateTemplate.
 */
public class ArtistHibernateDAO extends HibernateDaoSupport 
                                implements ArtistDAO {

  private static Logger log = 
    Logger.getLogger(ArtistHibernateDAO.class);
    
  /* (non-Javadoc)
   * @see com.oreilly.hh.dao.ArtistDAO#persist(com.oreilly.hh.data.Artist)
   */
  public Artist persist(Artist artist) { 1
    return (Artist) getHibernateTemplate().merge(artist);
  }
    
  /* (non-Javadoc)
   * @see com.oreilly.hh.dao.ArtistDAO#delete(com.oreilly.hh.data.Artist)
   */
  public void delete(Artist artist) { 2
    getHibernateTemplate().delete(artist);
  }

  /* (non-Javadoc)
   * @see com.oreilly.hh.dao.ArtistDAO#uniqueByName(java.lang.String)
   */
  public Artist uniqueByName(final String name) { 3
    return (Artist) getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(Session session) {
        Query query = getSession().getNamedQuery("com.oreilly.hh.artistByName");
        query.setString("name", name);
        return (Artist) query.uniqueResult();
      }
    });
  }
  
  /* (non-Javadoc)
   * @see com.oreilly.hh.dao.ArtistDAO#getArtist(java.lang.String, boolean)
   */
  public Artist getArtist(String name, boolean create) { 4
    Artist found = uniqueByName( name );
    if (found == null && create) {
      found = new Artist(name, new HashSet<Track>(), null);
      found = persist(found);
    }
    if (found != null && found.getActualArtist() != null) {
      return found.getActualArtist();
    }
    return found;
  }
}

1

This persist() method simply calls out to the merge() method of HibernateTemplate. The implementation of merge() looks at the id property of the artist parameter. If the id is null, merge() inserts a new row into the ARTIST table, and returns a new instance of Artist that has a populated id property. If the id is not null, merge() finds the matching row and updates it with the contents of the artist parameter.

2

delete() simply passes the artist parameter to the delete() method in HibernateTemplate. This method expects an object with a non-null id and deletes the corresponding row in the ARTIST table.

3

uniqueByName() is where things start to get more interesting. This is the first time in this class that we’ve referenced the Session object we’ve been using throughout this entire book. We’re using getSession() to retrieve a NamedQuery. This named Query is defined in the Artist class using the @NamedQuery annotation. We then proceed to set the named parameter name and retrieve a unique result. If there is no matching Artist in the database, uniqueResult() returns null. I’ll bet you also noticed that we’re using an anonymous instance of HibernateCallback and passing that to the HibernateTemplate object. For more information about HibernateCallback, see “How do I do that?” later in this chapter.

4

The getArtist() method really just rolls up calls to the other methods in ArtistDAO. This method attempts to retrieve an Artist by name by calling uniqueByName(). If no Artist is found and the create parameter is true, getArtist() creates a new instance of Artist with a null id and calls persist(). If no matching Artist is found and create is false, the method will return null. If a found or newly created Artist has a non-null actualArtist property, this method will return the value of artist.getActualArtist(). (The purpose of this step is explained in “Reflexive Associations” in Chapter 5.)

What HibernateDaoSupport provides

HibernateDaoSupport gives us a hook to the SessionFactory without having to know anything about how the Hibernate environment was created or configured. When we subclass HibernateDaoSupport our class then has access to a Hibernate Session via getSession() and a HibernateTemplate via getHibernateTemplate(). You already know what you can do with a Hibernate Session object (that’s why you read the previous 10 chapters). The interesting bits of the Spring/Hibernate integration are supplied by the HibernateTemplate class. Let’s delve into the details of this class.

To quote from the JavaDoc for HibernateTemplate: “This class can be considered [an] alternative to working with the raw Hibernate 3 Session API.” HibernateTemplate simplifies tasks that would otherwise be accomplished using the Session object, and it also translates HibernateExceptions to more general DataAccessExceptions. You use HibernateTemplate in one of two ways: you can call a set of simple helper functions such as load(), save(), delete(), or you can execute a HibernateCallback instance using the execute() method. The most common way you’ll find yourself using HibernateTemplate is through the simple helper functions—you only need to create a HibernateCallback object when you want to execute some Hibernate-specific code within the HibernateTemplate.

What is a DataAccessException? When we introduced the DAO pattern, it was an attempt to shield application code from the specifics of any one persistence API or library. It wouldn’t help us if our technology-neutral DAO threw a Hibernate-specific ObjectNotFoundException, so HibernateTemplate is responsible for handling any Hibernate-specific exceptions which may occure inside it. The Stripes API provides a simple way to tolerate such implementation-specific exceptions by wrapping them in its own generalized data access exceptions.

HibernateTemplate and HibernateCallback are the real workhorses that will help us avoid writing lines and lines of unnecessary Java code. Let’s use them both to reimplement the examples from the previous chapters.

How do I do that?

Before we use HibernateTemplate and HibernateCallback we need to run through a quick survey of the methods available to us.

HibernateTemplate provides a number of simple convenience methods that can turn multiple lines of direct Hibernate Session API code into simple one-liners. Let’s take a look at some examples of convenience methods to simplify querying a database table. Example 13-4 shows some examples of querying and finding objects.

Example 13-4. HibernateTemplate’s find() helpers
HibernateTemplate tmpl = getHibernateTemplate();

// All of these lines Find Artist with name 'Pavement'
List artists = tmpl.find("from com.oreilly.hh.data.Artist a " +
                         "  where a.name = 'Pavement'"); 1

String name = "Pavement";
List artists = tmpl.find("from com.oreilly.hh.data.Artist a " +
                         "  where a.name = ?", name); 2

List artists = tmpl.findByNamedParam("from com.oreilly.hh.data.Artist a " +
                                     "  where a.name = :name", "name", name); 3

// Assuming that there is a NamedQuery annotation "Artist.byName" on the 
// Artist class
List artists = tmpl.findByNamedQuery("Artist.byName", name); 4

Artist artist = new Artist();
artist.setName("Pavement");
List artists = tmpl.findByExample(artist); 5

// If we want to iterate through the result
Iterator artists = tmpl.iterate("from com.oreilly.hh.data.Artist " +
                                "  where a.name = ?", name); 6

// The following lines find all Artists
List artists = tmpl.find("from com.oreilly.hh.data.Artist"); 7
List artists = tmpl.loadAll(Artist.class);

The find methods are relatively straightforward:

1

There is a simple find() that takes an HQL query with no parameters.

2

This version of the method takes an HQL query and a single additional parameter. A similar version takes a query and an array of additional parameters: List find(String hql, Object[] params). These support the use of unnamed query parameters, but as we discussed in Chapter 3, there are better ways of writing queries.

3

The findByNamedParameter() method can handle queries with named parameters.

4

There is a findByNamedQuery() that allows you to quickly invoke a predefined HQL query, in this case named Artist.byName.

5

You can tie into Hibernate’s query-by-example capabilities using findByExample().

6

If you want to iterate through some results, you can call the iterate() method. When you call iterate(), Hibernate retrieves all of the IDs for matching rows and initializes elements as you iterate through the returned Iterator.

7

Lastly, if you just want to load all the rows from a given table, you can call find() or loadAll().

Tip

As discussed in “Better Ways to Build Queries” in Chapter 3, named queries are a good way to keep the query definitions out of your DAO code. If you are using Annotations, you define named queries using the @NamedQuery annotation. See Chapter 7 for more details on this annotation.

If we already know the value of the ID for a particular persistent object, HibernateTemplate provides helper methods for loading an object by an ID, as shown in Example 13-5.

Example 13-5. Loading objects with HibernateTemplate
// Identifier of Artist to load
Integer id = 1;

// Load an Artist object, return persistent Artist object
Artist artist = getHibernateTemplate().load(Artist.class, id);

// Populate the object passed in as a parameter.  Using the 
// object's type to specify the class
Artist artist = new Artist();
getHibernateTemplate().load(artist, id);

In the first example, we call the load() function with a Class and a Serializable ID value. Hibernate will then retrieve the row from the database and return an instance of the requested object. Instead of a Class object, you can also pass an object instance, Hibernate will use the type of the parameter to determine the class to retrieve.

We’ve examined the helpers on HibernateTemplate for querying and the helpers for loading. What about modifying rows in the database? Example 13-6 shows some examples that insert or update rows in the database.

Example 13-6. Saving and updating with HibernateTemplate
// Persist a new instance in the database
Artist a  = new Artist();
a.setName("Fischerspooner");
getHibernateTemplate().save(a);

// Load, modify, update a row in the database
Artist a = getHibernateTemplate().load(Artist.class, 1);
a.setName("Milli Vanilli");
getHibernateTemplate().update(a);

// Either insert or update depending on the identifier 
// of the object; associate resulting object with Session
Artist a = getHibernateTemplate().merge(a);

save() and update() are straightforward; both of these methods correspond to the similarly named methods on the Hibernate Session object. save() generates a new ID and inserts a new row in the table, and update() updates the matching row in the table. merge() is a bit more flexible: it examines the id of the parameter and calls either save() or update() depending on whether the ID is null.

You can also execute any arbitrary code that uses a Hibernate Session with a HibernateCallback. Before I try to explain exactly what this means, let’s take a look at Example 13-7.

Example 13-7. Writing a HibernateCallback
final String name = "Pavement";

Artist artist = (Artist) getHibernateTemplate().execute(new HibernateCallback() {
  public Object doInHibernate(Session session) {
    Criteria criteria = session.createCriteria(Artist.class);
    criteria.add(Restrictions.like("name", name));
    return criteria.uniqueResult();
  }
});

So what exactly is happening here? This example instantiates an anonymous inner class, which implements the HibernateCallback interface and passes it to the execute() method in HibernateTemplate. The HibernateCallback interface defines a single method doInHibernate(), which is executed with a Hibernate Session. The body of this method (as implemented in our anonymous inner class) uses the Hibernate Criteria API to generate a query that retrieves an Artist by name.

Why would we use the callback method when we could have easily obtained a reference to the Hibernate Session and created the same Criteria object? Even though we can access the Session object directly in HibernateDaoSupport using the getSession() method, we want to avoid direct calls to the Hibernate API because we don’t want to throw any Hibernate-specific exceptions (not even a RuntimeException). Remember your application is accessing this DAO via an interface, and it doesn’t know or care about the Hibernate-specific ObjectNotFoundException or about an exception in your HQL. Instead of accessing the Session object directly with getSession(), you can and should shield the rest of your application from such gory plumbing details by using a HibernateCallback to run any Hibernate API calls within the HibernateTemplate.

Where are the other DAOs?

Because they’re all very similar, it’s not worth wasting paper listing and talking about the others. You wouldn’t want to type in all that code anyway, so download the code examples if you’d like to look at them.

Creating an Application Context

When we introduced Spring we discussed how it would assume responsibility for creating and connecting the components in our application. For Spring to do this, we need to tell it about the various components (which Spring calls beans) in our system and how they are connected to each other. We do this using an XML document that describes the class of each bean, assigns it an ID, and establishes its relationships to other beans. Why the ID? In this context, an ID is a unique logical name for the bean, which is what you use to express relationships with other beans, and to request beans at runtime. In our example Spring configuration file we use logical names such as artistDao and albumDao. Each ID refers to a single component defined in the file.

This XML document is then used by Spring to create an ApplicationContext object from which we can retrieve our components by name. Figure 13-2 is a diagram of our application’s ApplicationContext.

Our Spring application context
Figure 13-2. Our Spring application context

From Figure 13-2 you can see that we have three test components that are connected to three DAO objects, and the DAO objects all have a reference to the sessionFactory object which is responsible for creating a Hibernate Session object and connecting to the database. This application is described by the Spring configuration file shown in Example 13-8, which you should name applicationContext.xml and place in the src directory.

Example 13-8. Spring 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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation=
         http://www.springframework.org/schema/beans http://www.springframework.
org/schema/beans/spring-beans-2.0.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org
/schema/tx/spring-tx-2.0.xsd"
       default-lazy-init="true"> 1
  <bean id="sessionFactory" 2
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFa
ctoryBean">
    <property name="annotatedClasses"> 3
      <list>
        <value>com.oreilly.hh.data.Album</value>
        <value>com.oreilly.hh.data.AlbumTrack</value>
        <value>com.oreilly.hh.data.Artist</value>
        <value>com.oreilly.hh.data.StereoVolume</value>
        <value>com.oreilly.hh.data.Track</value>
      </list>
    </property>
    <property name="hibernateProperties"> 4
      <props>
        <prop key="hibernate.show_sql">false</prop>
        <prop key="hibernate.format_sql">true</prop>
        <prop key="hibernate.transaction.factory_class">org.hibernate.transaction
.JDBCTransactionFactory</prop>
        <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect
</prop>
        <prop key="hibernate.connection.pool_size">0</prop>
        <prop key="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</prop
>
        <prop key="hibernate.connection.url">jdbc:hsqldb:data/music;shutdo
wn=true</prop>
        <prop key="hibernate.connection.username">sa</prop>
        <prop key="hibernate.connection.password"></prop>
      </props>
    </property>
  </bean>

  <!-- enable the configuration of transactional behavior based on annotations --
>
  <tx:annotation-driven transaction-manager="transactionManager"/> 5
  <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory">
      <ref local="sessionFactory"/>
    </property>
  </bean>

  <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBean
PostProcessor"/> 6

  <!-- Define our Data Access beans -->
  <bean id="albumDAO" class="com.oreilly.hh.dao.hibernate.AlbumHibernateDAO"> 7
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>
    
  <bean id="artistDAO" class="com.oreilly.hh.dao.hibernate.ArtistHibernateDAO">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

  <bean id="trackDAO" class="com.oreilly.hh.dao.hibernate.TrackHibernateDAO">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

  <!-- Define our Test beans -->
  <bean id="createTest" class="com.oreilly.hh.CreateTest"> 8
    <property name="trackDAO" ref="trackDAO"/>
    <property name="artistDAO" ref="artistDAO"/>
  </bean>

  <bean id="queryTest" class="com.oreilly.hh.QueryTest">
    <property name="trackDAO" ref="trackDAO"/>
  </bean>

  <bean id="albumTest" class="com.oreilly.hh.AlbumTest">
    <property name="albumDAO" ref="albumDAO"/>
    <property name="artistDAO" ref="artistDAO"/>
    <property name="trackDAO" ref="trackDAO"/>
  </bean>
</beans>

All right, that was quite a bit of XML to read through, wasn’t it? There are a lot of interesting things going on in this file, so let’s go over each section with a fine-toothed comb:

1

The top-level element is beans, and we have to declare some important namespaces for Spring to work properly. The http://www.springframework.org/schema/beans namespace is the default namespace that describes the elements for declaring beans, and the http://www.springframework.org/schema/tx namespace is used to define the annotation-driven transaction configuration, described later in this chapter. The default-lazy-init attribute controls the default behavior of the Spring IoC container: if this default setting is true, Spring will instantiate components only when they are requested. If default-lazy-init is set to false, Spring will instantiate beans during the initialization of the ApplicationContext.

2

sessionFactory is a bean that takes care of generating Session objects and dealing with connections to a JDBC DataSource. In general, the sessionFactory would work with a DataSource, and we would configure a Commons DBCP or C3P0 connection pool along side our SessionFactory in the applicationContext.xml. To keep this example contained, the sessionFactory contains properties that configure the JDBC connection directly.

3

As in the hibernate.cfg.xml file, we are defining all of the annotated classes that Hibernate needs to process.

4

The hibernateProperties element configures Hibernate. We’ll delve into more of the details of this section in “Hibernate configuration properties” later in this chapter.

5

The transactional annotation configuration is described in detail in “Transactions: the test interface” later in this chapter. The tx:annotation-driven element and the definition of the transactionManager allow us to use the Transactional annotation to define the scope and nature of any transactions in our application.

6

The RequiredAnnotationBeanPostProcessor is an unnamed component; its presence activates the enforcement of the Required annotation on setter methods. If you put the Required attribute on the setter of a required bean property, Spring will validate that this property was set after initializing a bean. This is used in the test classes to make sure that Spring has configured our DAO dependencies.

7

The DAO objects are all defined here: albumDAO, artistDAO, and trackDAO.

8

The test beans are all defined here: createTest, queryTest, and albumTest.

Hibernate configuration properties

Taking a closer look at the hibernateProperties in Example 13-8, you will notice that there are a number of interesting configuration properties. Let’s examine each of them:

hibernate.connection.driver_class, hibernate.connection.url, hibernate.connection.username, hibernate.connection.password

These configuration properties take care of configuring the JDBC connection to the database. These properties should be familiar from previous chapters; the values in applicationContext.xml are the same as the values we used earlier in hibernate.cfg.xml and hibernate.properties.

hibernate.connection.pool_size

This property sets the size of the internal Hibernate connection pool. Instead of using the Hibernate connection pool, you could also use Hibernate’s built-in support for Apache Commons DBCP or C3P0, both of which are good choices if you are deploying a production system. If this property is set to a nonzero value, Hibernate will attempt to recycle and reuse connections to the database.

This is an interesting case, because we’re setting the pool_size to zero, because I want to turn off connection pooling for this example to making working with HSQLDB a little easier. HSQLDB expects a SHUTDOWN command when the last connection is terminated, and because I don’t want to write and configure a special shutdown hook, I’m simply making sure that my JDBC Connection object is closed when I’m done with it.

hibernate.dialect

Here we set the Hibernate dialect. For a list of available dialects, see Appendix C.

hibernate.transaction.factory_class

In this example, we’re using the JDBC driver to manage our transactions. In a more complex deployment environment using JTA we might configure this with org.hibernate.transaction.JTATransactionFactory if we were using container-managed transactions.

hibernate.show_sql, hibernate.format_sql

If show_sql is set to true, Hibernate will print out the SQL it is executing. This can be very helpful if you are trying to debug Spring and figure out how a specific mapping is trying to access the database table. If format_sql is true, the SQL statement is formatted; if format_sql is false, the SQL is printed on one line.

Putting It All Together

All of this Spring configuration is useless if we don’t know how to create a Spring ApplicationContext and run our code. In this section, we’re going to adapt the CreateTest, QueryTest, and AlbumTest classes used in previous examples to implement a Test interface rather than expecting them to be run from the command line directly, and create a TestRunner to execute these test objects from our Spring ApplicationContext.

Transactions: the test interface

Later in this chapter, we’ll write a class named TestRunner which knows how to retrieve a bean from a Spring ApplicationContext, which is expected to implement the Test interface, and execute that bean’s run() method. The beans it uses will be adaptations of the CreateTest, QueryTest, and AlbumTest classes from the previous chapters. To work in this new way, we’ll have them each implement a common interface called Test, shown in Example 13-9.

Example 13-9. The Test interface
package com.oreilly.hh;
import org.springframework.transaction.annotation.Transactional;

/**
 * A common interface for our example classes.  We'll need this
 * because TestHarness needs to cast CreateTest, QueryTest, or 
 * AlbumTest to a common interface after it retrieves the bean
 * from the Spring application context.
 */
public interface Test {
    /**
     * Runs a simple example
     */
    @Transactional(readOnly=false)
    public void run();
}

This Test interface serves as a common interface for use by TestRunner, and it also gives us a convenient method to annotate with the Transactional annotation. The Transactional annotation takes care of binding a Session to the current Thread, starting a transaction, and either committing the transaction if the method returns normally, or rolling it back if there is an exception.

For more information about the @Transactional annotation, please see Appendix D.

How do I activate the transactional annotation?

To turn on the processing of the Transactional annotation, we used this chunk of configuration in our applicationContext.xml:

  <!-- enable the configuration of transactional behavior based on annotations -->
  <tx:annotation-driven transaction-manager="transactionManager"/> 
  <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory">
      <ref local="sessionFactory"/>
    </property>
  </bean>

The tx:annotation-driven element simply activates the Transactional annotation and points it to a PlatformTransactionManager. HibernateTransactionManager is an implementation of the Spring Framework’s PlatformTransactionManager. It takes care of binding a Hibernate Session from the sessionFactory to the current Thread using SessionFactoryUtils. Since our DAO objects all extend HibernateDaoSupport and use the HibernateTemplate, these persistent objects are able to participate in transactions and obtain the same thread-bound Session object. This isn’t just useful for dealing with transactions, it is essential when we’re working with lazy associations.

The Transactional annotation ensures that the same Session will remain open and bound to the current Thread during the execution of the annotated method. Without this annotation, Hibernate would create a new Session for every operation that needed a Session, and you would be unable to fetch any associations of the objects you had retrieved using Hibernate.

Why is this? Let’s back track a bit to topics we brought up in Chapter 5. Recall that in Hibernate 3, associations between mapped objects default to lazy loading. Unless you change this explicitly for a particular class or association, related objects are not retrieved from the database until you’ve traversed to a particular object. For example, if you retrieve an Album object from the database, the List of AlbumTrack objects is not retrieved until you call the album.getAlbumTracks() method. To accomplish this, Hibernate does two things:

  1. Hibernate returns a “proxy” object that stands in for the not-yet-loaded object. When you retrieve a Track object, the object returned is a Track, but associated collections such as track.getArtists() are instances of PersistentSet.

  2. A PersistentSet is something Hibernate manages, and you don’t normally need to think much about it. What is pertinent to this discussion is that it is an implementation of PersistentCollection and it contains a reference to a Session object. In other words, that PersistentSet is involved in fetching the related Artists on an as-needed basis. You’ll get a Track back, but you won’t fetch any Artist objects until you call track.getArtists(), and they need to come through the Session.

Fetching lazy associations only works if the PersistentSet is referencing an active Session. Without an open session, attempting to traverse a lazy association will throw an exception. In a web application, you might use something like Spring’s OpenSessionInViewFilter to make sure you have a reference to a Session throughout a single request. In this application, we’re relying on the Transactional annotation to make sure that all of the code in any run() method implementation has access to the same Hibernate Session object.

Adapting CreateTest, QueryTest, and AlbumTest

Now that we’ve got our Test interface defined and set up to provide a stable transaction environment to its implementations, we can revise CreateTest, QueryTest, and AlbumTest. First is the adaptation of CreateTest shown in Example 13-10.

Example 13-10. CreateTest adapted for use in our Spring context
package com.oreilly.hh;

import java.sql.Time;
import java.util.*;
import com.oreilly.hh.dao.*;
import com.oreilly.hh.data.*;

/**
 * Create sample data, letting Hibernate persist it for us.
 */
public class CreateTest implements Test {

    private ArtistDAO artistDAO;
    private TrackDAO trackDAO;

    /**
     * Utility method to associate an artist with a track
     */
    private static void addTrackArtist(Track track, Artist artist) {
        track.getArtists().add(artist);
    }

    /* (non-Javadoc)
     * @see com.oreilly.hh.Test#run()
     */
    public void run() {
        StereoVolume fullVolume = new StereoVolume();

        Track track = new Track("Russian Trance", "vol2/album610/track02.mp3",
                Time.valueOf("00:03:30"), new HashSet<Artist>(), new Date(),
                fullVolume, SourceMedia.CD, new HashSet<String>());
        addTrackArtist(track, artistDAO.getArtist("PPK", true));
        trackDAO.persist(track);
    }

    public ArtistDAO getArtistDAO() { return artistDAO; }
    public void setArtistDAO(ArtistDAO artistDAO) {
        this.artistDAO = artistDAO;
    }

    public TrackDAO getTrackDAO() { return trackDAO; }
    public void setTrackDAO(TrackDAO trackDAO) {
        this.trackDAO = trackDAO;
    }
}

Notice that CreateTest has two private member variables, artistDAO and trackDAO, both of which are made visible with accessor methods as bean properties. Then we have a simple run() method, as mandated by the Test interface, which in this case creates an instance of Track, associates an Artist with the Track, and then persists the Track object with a call to trackDAO.makePersistent(). That’s it—no try/catch/finally blocks and no mention of a transaction. We’ve offloaded almost everything to the Spring framework with the help of our DAO. Example 13-11 is an excerpt from applicationContext.xml in which an instance of this CreateTest class is created as a bean with the ID createTest, and the bean properties artistDAO and trackDAO are populated with references to the DAO beans.

Example 13-11. Configuring the createTest bean
<bean id="createTest" class="com.oreilly.hh.CreateTest"> 
  <property name="trackDAO" ref="trackDAO"/>
  <property name="artistDAO" ref="artistDAO"/>
</bean>

Compare this implementation of CreateTest to the original version in Example 3-3. You can hardly compare them. The non-Spring version of CreateTest had to take care of Session creation, transaction management, exception handling, and configuration. The new version doesn’t even mention the Session object. In fact, there’s nothing Hibernate-specific in this latest CreateTest: the DAOs prevent our application logic from having to deal directly with the underlying persistence mechanism. In other words, once you get your mind around the Spring Framework and get it set up, it is an order of magnitude easier than working with Hibernate directly. See Example 13-12.

Example 13-12. QueryTest adapted for use with Spring
package com.oreilly.hh;

import java.sql.Time;
import java.util.List;
import org.apache.log4j.Logger;
import com.oreilly.hh.dao.TrackDAO;
import com.oreilly.hh.data.Track;

/**
 * Retrieve data as objects
 */
public class QueryTest implements Test {

    private static Logger log = Logger.getLogger(QueryTest.class);

    private TrackDAO trackDAO;

    public void run() {
        // Print the tracks that will fit in five minutes
        List<Track> tracks = trackDAO.tracksNoLongerThan(
          Time.valueOf("00:05:00")); 
        for (Track track : tracks) {
            // For each track returned, print out the 
            // title and the playTime
            log.info("Track: "" + track.getTitle() + "", "
                    + track.getPlayTime());
        }
    }

    public TrackDAO getTrackDAO() { return trackDAO; }
    public void setTrackDAO(TrackDAO trackDAO) {
        this.trackDAO = trackDAO;
    }
}

The reimplementation of QueryTest also defines a private member variable referencing the TrackDAO object, and the run() method invokes the method trackDAO.tracksNoLongerThan(), passing it a Java.sql.Time instance of 5 minutes. This code then loops through the results and prints out the Track’s title and playTime properties using Log4J. See Example 13-13.

Example 13-13. Reimplementing AlbumTest
package com.oreilly.hh;

import java.sql.Time;
import java.util.*;
import org.apache.log4j.Logger;
import com.oreilly.hh.dao.*;
import com.oreilly.hh.data.*;

/**
 * Create sample album data, letting Hibernate persist it for us.
 */
public class AlbumTest implements Test {

  private static Logger log = Logger.getLogger( AlbumTest.class );
    
  private AlbumDAO albumDAO; 1
  private ArtistDAO artistDAO;
  private TrackDAO trackDAO;
    
  public void run() {
    // Retrieve (or create) an Artist matching this name
    Artist artist = artistDAO.getArtist("Martin L. Gore", true); 2

    // Create an instance of album, add the artist and persist it 
    // to the database.
    Album album = new Album("Counterfeit e.p.", 1,
      new HashSet<Artist>(), new HashSet<String>(),
      new ArrayList<AlbumTrack>(5), new Date());
    album.getArtists().add(artist);
    album = albumDAO.persist(album); 3

    // Add two album tracks
    addAlbumTrack(album, "Compulsion", "vol1/album83/track01.mp3",
                  Time.valueOf("00:05:29"), artist, 1, 1);
    addAlbumTrack(album, "In a Manner of Speaking",
                  "vol1/album83/track02.mp3", Time.valueOf("00:04:21"),
                          artist, 1, 2);

    // persist the album
    album = albumDAO.persist( album ); 4

    log.info(album);
  }

  /**
   * Quick and dirty helper method to handle repetitive portion of creating
   * album tracks. A real implementation would have much more flexibility.
   */
  private void addAlbumTrack(Album album, String title, String file,
                             Time length, Artist artist, int disc,
                             int positionOnDisc) {
    // Create a new Track object and add the artist
    Track track = new Track(title, file, length, new HashSet<Artist>(),
                            new Date(), new StereoVolume(), SourceMedia.CD,
                            new HashSet<String>());
    track.getArtists().add(artist);

    // Persist the track to the database
    track = trackDAO.persist(track);

    // Add a new instance of  AlbumTrack with the persisted 
    // album and track objects
    album.getTracks().add(new AlbumTrack(track, disc, positionOnDisc));
  }

  public AlbumDAO getAlbumDAO() { return albumDAO; }
  public void setAlbumDAO(AlbumDAO albumDAO) {
    this.albumDAO = albumDAO;
  }

  public ArtistDAO getArtistDAO() { return artistDAO; }
  public void setArtistDAO(ArtistDAO artistDAO) {
    this.artistDAO = artistDAO;
  }

  public TrackDAO getTrackDAO() { return trackDAO; }
  public void setTrackDAO(TrackDAO trackDAO) {
    this.trackDAO = trackDAO;
  }
}

AlbumTest is more complex than either CreateTest or QueryTest because it deals with the creation and persistence of multiple objects and the side-effects of cascading. Let’s step through the code:

1

Just as CreateTest and QueryTest did, the AlbumTest class defines a series of private fields that reference all of the DAO objects it needs: trackDAO, artistDAO, and albumDAO.

2

AlbumTest retrieves an Artist using artistDAO.getArtist(), which creates a new Artist if it cannot find the artist you’ve requested.

3

The Album instance is persisted. This creates a row in the database and returns an Album object with a non-null id property. We’re persisting the Album record now so we can use the new instance of Album to create Track objects and then relate them to the new Album object. For this to work properly, we’re going to need to make sure that our Album and Track objects have non-null id properties.

4

We then add a series of Track objects. To create the Track objects, we first create a new instance of Track, and then persist the Track object with trackDAO.persist(). In the addAlbumTrack() method, we create Track objects and combine them with Albums in the AlbumTrack relationship object. The tracks property on Album has a OneToMany relationship with cascade set to CacscadeType.ALL, so when we persist the album object again, it will automatically create rows in ALBUM_TRACKS.

That’s the extent of our Test implementations. The general recipe here was to migrate all of the persistence code to our DAOs and then to migrate our standalone CreateTest, QueryTest, and AlbumTest classes to beans with properties referencing these DAOs, with the actual test code moved into a run() method as required by the Test interface. This lets the Spring Framework wire all of our components together. In the next section we’ll see how all of this is executed.

TestRunner: loading a Spring ApplicationContext

All this code is useless if we don’t have a way of loading a Spring ApplicationContext and executing our Test objects. For this, we’ll create a TestRunner class with a static main() method to be executed from our Ant build.xml. Example 13-14 is a complete listing of TestRunner. This class takes care of loading our Spring ApplicationContext, retrieving a Test implementation, and executing it.

Example 13-14. Loading a Spring application context
package com.oreilly.hh;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * A simple harness to run our tests.  Configures Log4J,
 * creates an ApplicationContext, retrieves a bean from Spring
 */
public class TestRunner {

  private static Logger log;
    
  public static void main(String[] args) throws Exception {
    // Configure Log4J from a properties file
    PropertyConfigurator.configure(
      TestRunner.class.getResource("/log4j.properties")); 1
    log = Logger.getLogger(TestRunner.class);
        
    // Load our Spring Application Context
    log.info( "Initializing TestRunner..." );
    log.info( "Loading Spring Configuration..." );
    ApplicationContext context = 2
      new ClassPathXmlApplicationContext("applicationContext.xml");

    // Retrieve the test name from the command line and 
    // run the test.
    String testName = args[0];
    log.info( "Running test: " + testName );
    Test test = (Test) context.getBean(testName); 3
    test.run();
  }
}

TestRunner takes care of three things for us, as noted in the JavaDoc:

1

Configure Log4J by referencing log4j.properties at the root of the class path.

2

Create a Spring ApplicationContext object using the ClassPathXmlApplicationContext object. The ClassPathXmlApplicationContext constructor takes a String that specifies the path of the Spring XML configuration on the class path. In this instance, our applicationContext.xml is at the root of the class path (right next to our log4j.properties file).

3

Lastly, we get the name of the bean from the command-line arguments, and we retrieve this Test object from the ApplicationContext. As you can see, it’s very easy to retrieve a named bean from the ApplicationContext: just call context.getBean(name) and cast the result to the expected type.

Running CreateTest, QueryTest, and AlbumTest

To run TestRunner and retrieve the appropriate bean from our Spring ApplicationContext, we need to modify our Ant build.xml script. Find the targets named ctest, qtest, and atest, and change them to contain the following XML, as shown in Example 13-15.

Example 13-15. Executing TestRunner from Ant
<target name="atest" description="Creates and persists some album data" depends=
"compile">
  <java classname="com.oreilly.hh.TestRunner" fork="true">
    <classpath refid="project.class.path" />
    <arg value="albumTest"/>
  </java>
</target>

<target name="ctest" description="Creates and persists some sample data"
        depends="compile">
  <java classname="com.oreilly.hh.TestRunner" fork="true" failonerror="true">
    <classpath refid="project.class.path" />
    <arg value="createTest"/>
  </java>
</target>

<target name="qtest" description="Runs a query" depends="compile">
  <java classname="com.oreilly.hh.TestRunner" fork="true">
    <classpath refid="project.class.path" />
    <arg value="queryTest"/>
  </java>
</target>

The TestRunner class uses its first command-line argument as the name of the bean to retrieve from the Spring ApplicationContext. In the build.xml we are invoking TestRunner and passing in the name of the bean (from applicationContext.xml) as an argument.

To create the test database, run ant schema as usual, and to insert data into the database, run our new version of ant ctest:

% ant schema
% ant ctest
Buildfile: build.xml

prepare:

compile:

ctest:
     [java]  INFO TestRunner:20 - Initializing TestRunner...
     [java]  INFO TestRunner:21 - Loading Spring Configuration...
     [java]  INFO TestRunner:25 - Running test: createTest

BUILD SUCCESSFUL
Total time: 3 seconds

Run ant qtest to invoke the new QueryTest example and confirm that everything we put together worked as expected:

% ant qtest
Buildfile: build.xml

prepare:

compile:

qtest:
     [java]  INFO TestRunner:20 - Initializing TestRunner...
     [java]  INFO TestRunner:21 - Loading Spring Configuration...
     [java]  INFO TestRunner:25 - Running test: queryTest
     [java]  INFO QueryTest:25 - Track: "Russian Trance", 00:03:30
     [java]  INFO QueryTest:25 - Track: "Video Killed the Radio Star", 00:03:49
     [java]  INFO QueryTest:25 - Track: "Test Tone 1", 00:00:10

BUILD SUCCESSFUL
Total time: 3 seconds

Finally, we can run the new AlbumTest example. Type ant atest, and you should see the following output:

% ant atest
Buildfile: build.xml

prepare:

compile:

atest:
     [java]  INFO TestRunner:16 - Initializing TestRunner...
     [java]  INFO TestRunner:17 - Loading Spring Configuration...
     [java]  INFO TestRunner:21 - Running test: albumTest
     [java]  INFO AlbumTest:40 - Persisted Album: 1
     [java]  INFO AlbumTest:59 - Saved an album named Counterfeit e.p.
     [java]  INFO AlbumTest:60 - With 2 tracks.

BUILD SUCCESSFUL
Total time: 2 seconds

It worked! Now what?

The Spring Framework and Hibernate complement each other quite nicely, and if you are about to adopt Hibernate for a large application, you should consider basing your application on the Spring Framework. Once you’ve invested the time to learn the framework, we’re certain you’ll find yourself writing less transaction handling, connection management, and Hibernate session management code. The less time you have to spend on these tasks, the more time you can devote to your application’s specific requirements and logic. Portability is another reason to use Spring (or any IoC container, for that matter) and to start using patterns like the DAO. While Hibernate is the first choice among persistence libraries today, there’s no telling what the next decade will bring. If you isolate your Hibernate-specific code from the rest of your application, it’ll be that much easier to experiment with the next technology that comes down the road.

Note

Use Spring. It takes care of the tedious work. But don’t use it as an excuse not to learn the details of Hibernate.

Take care not to be deceived by the simplicity of Hibernate when coupled with Spring. The authors of this book unanimously agreed that while Hibernate was a great thing, it can sometimes be difficult to debug and diagnose for any number of reasons: a single-character typo, a badly mapped table, a slightly incorrect flush mode, or some arcane incompatibility with a JDBC driver. Spring makes Hibernate easy because it provides some useful abstractions—you gain simplicity, but it takes you that much further away from executing a SQL statement against a database. While you might not have to write your own transaction handling code, there will be times when these abstractions can make it more difficult to diagnose the root cause of an error. Don’t get me wrong—I would not use Hibernate without Spring, but you’ll be better able to diagnose problems is you have a solid grounding in the details of Hibernate.

In the next chapter, we’ll show you how to take the next step—how to integrate Hibernate into a web application framework called Stripes. In this web application, you’ll see how Spring serves as a neutral broker between Stripes and Hibernate. As you read that chapter, you should keep in mind the fact that most of the popular web application frameworks in use today have some facility for direct integration with Spring. If you use Struts 2, Wicket, or Spring MVC, many of the concepts remain the same. Spring is something of a Rosetta Stone for software, and once you adopt it you have access to all the libraries which were designed to interoperate with Spring. Use Spring as your foundation, and you’ll have an easier time swapping in different technologies as your requirements change. Among some of the possibilities are writing DAO components in JRuby or Groovy instead of Java, integrating a cron-like facility using Quartz, and exposing service objects as SOAP endpoints using libraries like Apache CXF.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset