Lazy loading: An object that doesn't contain all of the data you need but knows how to get it. (Martin Fowler, Patterns of Enterprise Application Architecture)
I have recently explored the wonderful world of lazy loading. The original title of this article was going to be "Lazy Loading Made Easy." It originally started out as an exercise to try something complicated, but as it turns out, lazy loading already is easy.
First off: why would you want to do lazy loading? The most important reason is to be able to have a clean domain model. Consider the (pseudo) class Category:
public class Category {
public Collection<Category> subcategories = new HashSet<Category>();
public Category parent;
public String name;
public int creationYear;
}
With this class, I would be able to say stuff like:
This should print all the siblings of category 1 (whatever that means). However, without lazy loading, I would have to do this:
Category category = dao.get(1);
Category parent = dao.get(category.parentId);
List children = new ArrayList();
for (Long childId : parent.subcategoryIds) {
children.add(dao.get(childId));
}
System.out.println(children);
This might not look so bad in this simple example, but it adds up. In addition, look at all the places where we need the DAO! In effect, this rips the whole business logic out of the domain objects, as they normally cannot access the DAOs directly. If this code looks unpleasantly familiar, this article is for you.
In order to do the first, easy example efficiently, we have to support lazy loading.
Smoke Testing the DAO
I will start out by writing a simple test to demonstrate the implementation
of a basic DAO. We will then expand this DAO to support lazy loading.
We start with a simple test for saving and retrieving objects:
public class CategoryDaoTest extends TestCase {
public void testSaveRetrieve() throws Exception {
Category parent = new Category("parent", 2005);
CategoryDao categoryDao = new SpaceCategoryDao();
categoryDao.store(parent);
Category saved = categoryDao.get(parent.getId());
assertNotSame(saved, parent);
assertEquals("name", saved.getName(), parent.getName());
}
}
This only verifies
that saving and retrieving objects without lazy loading works
correctly. I have created two implementations of the
CategoryDAO: one for JDBC and one using a simple internal
structure (I call this a space, because it is vaguely inspired by JavaSpaces).
I show this example using the simpler spaces structure. For the JDBC
implementation, see the downloadable source. Here are the
get() and store() methods of
SpaceCategoryDao:
public Category get(Long id) {
if (!idToTuples.containsKey(id)) {
return null;
}
Serializable[] tuple = (Serializable[]) idToTuples.get(id);
return tupleToObject(tuple);
}
public Long store(Category category) {
if (category == null) {
return null;
}
if (idToTuples.containsKey(category.getId())) {
return category.getId();
}
store(category.getParent());
category.setId(new Long(nextId++));
Serializable[] tuple = objectToTuple(category);
idToTuples.put(category.getId(), tuple);
for (Iterator iter = category.getSubcategories().iterator(); iter.hasNext();) {
store((Category) iter.next());
}
return category.getId();
}
The objects are saved by using objectToTuple and restored by tupleToObject:
We now have basic storing and loading of a single object implemented. Using test-driven development, I know that it basically works. We are now set up for solving the problem of lazy loading. First, I will show how we load the related objects without lazy loading, and then I will improve the implementation to load the parent relationship lazily.
Lazy Loading Interfaces
The code above does not deal with the relationships, but the test passes, so I write a new test:
This works, but it doesn't do lazy loading. However, we can replace it by a dynamic proxy. In order to do so, I have to extract an interface (called CategoryItf) to the Category class that contains those methods that I want to use in the parent. This is a bit of a hassle, but as we shall see later, we can simplify this further. Here is the new implementation of tupleToObject
Okay, I am pulling your leg. Here is the implementation of lazyGet:
protected CategoryItf lazyGet(Long id) {
if (id == null) {
return null;
}
return (CategoryItf)Proxy.newProxyInstance(
CategoryItf.class.getClassLoader(),
new Class[] { CategoryItf.class },
new LazyLoadedObject() {
protected Object loadObject() {
return get(id);
}
});
}
In order to understand this code, you have to understand dynamic proxies, which were introduced in Java 1.3. The java.lang.reflect.Proxy.newInstance() method will return a dynamically generated object that implements the interfaces given to the method call (in this case CategoryItf), and calls an invocation handler no matter what method is called on this interface. The code passes in an anonymous subclass of a custom class named LazyLoadedObject. Here is the LazyLoadedObject invocation handler:
public abstract class LazyLoadedObject
implements InvocationHandler {
private Object target;
public Object invoke(Object proxy,
Method method, Object[] args)
throws Throwable {
if (target == null) {
target = loadObject();
}
return method.invoke(target, args);
}
protected abstract Object loadObject();
}
This makes the lazy loading work, but we have to implement an extra interface just for the dynamic proxy. Let's see if we can do it the way the persistence frameworks do it: with bytecode generation.