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.
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.
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.
Enough with this introduction. Let’s get to the code.
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.
<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.
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.
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.
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.
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.
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.
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.
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.
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.
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) { return (Artist) getHibernateTemplate().merge(artist); } /* (non-Javadoc) * @see com.oreilly.hh.dao.ArtistDAO#delete(com.oreilly.hh.data.Artist) */ public void delete(Artist artist) { getHibernateTemplate().delete(artist); } /* (non-Javadoc) * @see com.oreilly.hh.dao.ArtistDAO#uniqueByName(java.lang.String) */ public Artist uniqueByName(final String name) { 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) { 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; } }
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.
delete()
simply passes the
artist parameter to the
delete()
method in Hibernate
Template
. This method expects an
object with a non-null
id
and deletes the corresponding row in the
ARTIST
table.
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.
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.)
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
HibernateException
s to more general
DataAccessException
s. 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.
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.
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'"); String name = "Pavement"; List artists = tmpl.find("from com.oreilly.hh.data.Artist a " + " where a.name = ?", name); List artists = tmpl.findByNamedParam("from com.oreilly.hh.data.Artist a " + " where a.name = :name", "name", name); // Assuming that there is a NamedQuery annotation "Artist.byName" on the // Artist class List artists = tmpl.findByNamedQuery("Artist.byName", name); Artist artist = new Artist(); artist.setName("Pavement"); List artists = tmpl.findByExample(artist); // If we want to iterate through the result Iterator artists = tmpl.iterate("from com.oreilly.hh.data.Artist " + " where a.name = ?", name); // The following lines find all Artists List artists = tmpl.find("from com.oreilly.hh.data.Artist"); List artists = tmpl.loadAll(Artist.class);
The find
methods are relatively
straightforward:
There is a simple find()
that
takes an HQL query with no parameters.
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.
The findByNamedParameter()
method can handle queries with named parameters.
There is a findByNamedQuery()
that allows you to quickly invoke a predefined
HQL query, in this case named
Artist.byName
.
You can tie into Hibernate’s query-by-example capabilities
using findByExample()
.
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
.
Lastly, if you just want to load all the rows from a given
table, you can call find()
or loadAll()
.
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.
// 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.
// 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.
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
.
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
.
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.
<?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"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFa ctoryBean"> <property name="annotatedClasses"> <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"> <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"/> <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"/> <!-- Define our Data Access beans --> <bean id="albumDAO" class="com.oreilly.hh.dao.hibernate.AlbumHibernateDAO"> <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"> <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:
The top-level element is
, and we have to
declare some important namespaces for Spring
to work properly. The
beans
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
.
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.
As in the hibernate.cfg.xml file, we are defining all of the annotated classes that Hibernate needs to process.
The hibernateProperties
element configures Hibernate. We’ll delve into more
of the details of this section in “Hibernate configuration properties”
later in this chapter.
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.
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.
The DAO objects are all defined here:
albumDAO
, artistDAO
, and
trackDAO
.
The test beans are all defined here:
createTest
, queryTest
, and
albumTest
.
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.
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
.
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.
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.
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:
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.
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 Open
Session
InViewFilter
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.
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.
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.
<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.
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.
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; 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); // 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); // 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 ); 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:
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
.
AlbumTest
retrieves an
Artist
using
artistDAO.getArtist()
, which creates a
new Artist
if it cannot find the artist
you’ve requested.
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.
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.
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.
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")); log = Logger.getLogger(TestRunner.class); // Load our Spring Application Context log.info( "Initializing TestRunner..." ); log.info( "Loading Spring Configuration..." ); ApplicationContext context = 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); test.run(); } }
TestRunner
takes care of three things
for us, as noted in the JavaDoc:
Configure Log4J by referencing log4j.properties at the root of the class path.
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).
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.
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.
<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
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.
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.