In the Java world, there have been many ways to persist and retrieve domain objects, and most involve some form of ORM mapping. Modern methods include Hibernate, iBatis, Spring JDBC Templates and other JPA providers; these are all fine methods and all should be used appropriately. Most of the time you should use one of the above approaches and not what I’m going to show you below.
One thing I’d like to say about persistence in Java is that you should almost never be using raw JDBC. There’s too much boiler-plate and repetition. You need to create a DataSource
, and then get a Connection
, then use that Connection
to get a PreparedStatement
. That can start to look pretty messy.
Worst of all is that you get a ResultSet
back which, if used without any form of conversion or mapping to your own domain, exposes the rest of your application to the persistence layer. This is bad for several reasons, not least of all it makes your code more tightly coupled and brittle in the face of change. Let’s be clear here: passing around a ResultSet
, CachedRowSet
or any other variant is BAD.
It’s easy to avoid this problem by convert between a ResultSet row and a domain-specific object. Spring popularised an idea called the RowMapper (which I’ll outline below). The idea is that you have an interface that, once implemented, will convert between a resultSet and your domain object.
Why might you need this? If you’re dealing with legacy applications, it’s frequently too costly to go back and retrofit one of the more modern approaches to ORM; the database layer doesn’t fit well with Hibernate, or the rest of the codebase won’t sit nicely with JPA, or you can’t take an (additional) external library dependency for any number of reasons. Sometimes it’s easier to quickly roll your own. Here’s an example I’ll call a DataMapper
(NOTE: I’ve omitted Exception handling for the sake of clarity, you will need to add this back in!):
public interface DataMapper<T> {
public T mapRow(ResultSet resultSet);
}
That’s nice and simple. When you implement this interface, your implementation doesn’t bother with any sort of row iteration, all it does is take the current row of the resultSet
, call the standard getString(...)
etc methods and builds up a single one of your beans, here represented by the generic placeholder T
. Elsewhere you have this partner method (I like to keep this as a statically imported utility):
public static <T> Collection<T> map(ResultSet resultSet, DataMapper<T> mapper) {
Collection<T> mappedObjects = new LinkedList<T>();
while(resultSet.next() {
mappedObjects.add(mapper.mapRow(resultSet));
}
return mappedObjects;
}
This method takes care of the iteration. You pass it your resultSet, and a DataMapper implementation and it’ll give you back a collection of your domain objects. Still quite simple and neat.
How about an example? Let’s say you have a ResultSet that could be mapped to a simple Person bean:
public class Person {
private String firstName;
private String lastName;
private int age;
//getters and setters for the above fields are omitted
//Imagine they were below. Go on, it'll make life easier.
}
To do this using the DataMapper code I’ve outlined above, you’d have something like this:
public class PersonMapper implements DataMapper<Person> {
@Override
public Person mapRow(ResultSet resultSet) {
Person bean = new Person();
bean.setFirstName(resultSet.getString("first_name"));
bean.setLastName(resultSet.getString("last_name"));
bean.setAge(resultSet.getInt("age"));
return bean;
}
}
Pass that to the map method along with a valid ResultSet and you’ll get out a Collection of Person beans to work with. We’ve gone from ResultSet to domain-specific in very little code.
Now these DataMappers don’t have to be simple. They could use the ResultSet and only the ResultSet, or they could use that as a basis to do more queries to build the object. They could lookup other services for information. They can do whatever you want for a relatively high power-to-lines of code ratio.
I’ve found myself using the DataMapper abstraction for the purposes of unit testing. You write a DataMapper that outright ignores the (perhaps empty) ResultSet you pass it, and it instead uses the some randomised test data you’ve got ready. You can now easily create test beans without relying on the DB layer.
It’s not perfect, and there are often better ways of doing persistence, but I’ve found this pattern has been very useful on a number of occasions.