Hello Lazy Loading
Due to popular demand, I will post a very short version of my Lazy Loading article:
Why?
Because this is bad:
Category category = dao.get(1);
Category parent = dao.get(category.parentId);
int sumChildValue = 0;
for (Long childId : parent.subcategoryIds) {
sumChildValue += dao.get(childId).getValue();
}
System.out.println(sumChildValue);
This is good:
Category category = dao.get(1);
int sumChildValue = 0;
for (Category sibling : category.getParent().getChildren()) {
sumChildValue += sibling.getValue();
}
System.out.println(sumChildValue);
And this is better:
System.out.println(dao.get(1).getParent().getChildSum());
Where getChildSum() is:
public int Category.getChildSum() {
int sumChildValue = 0;
for (Category child : getChildren()) {
sumChildValue += child.getValue();
}
return sumChildValue;
}
How?
Using Java 1.3 Dynamic Proxies (interfaces only)
public abstract class LazyLoadedObject implements java.lang.reflect.InvocationHandler {
private Object target;
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
if (target == null) {
target = loadObject();
}
return method.invoke(target, args);
}
protected abstract Object loadObject();
}
category.setChildren((Collection) java.lang.reflect.Proxy.newProxy(
Collection.class.getClassLoader(),
new Class[] { Collection.class },
new LazyLoadedObject() {
protected Object loadObject() {
// do whatever - e.g.
return dao.findByParentId(category.getId());
}
}));
Using cglib (also classes)
category.setParent(net.sf.cglib.proxy.Enhancer.create(
Category.class,
new net.sf.cglib.proxy.LazyLoader() {
protected Object loadObject() {
// do whatever - e.g.
return dao.get(category.getParentId());
}
}));
Testing (example with EasyMock)
import org.easymock.EasyMock;
public void testLazyGet() {
Long parentId = new Long(1);
Category parent = new Category("foo", 1998);
Collection children = Arrays.asList(
new Object[] { new Category("bar", 1999), new Category("baz", 2000) });
CategoryDao daoMock = EasyMock.createMock(CategoryDao.class);
LazyLoadedDao dao = new LazyLoadedDao(daoMock);
EasyMock.expect(daoMock.get(parentId)).andReturn(parent);
EasyMock.replay(daoMock);
Category category = dao.get(parentId);
EasyMock.verify(daoMock);
assertEquals(category, parent);
// This checks that we don't load the children before we're really asked to. Cool, eh?
EasyMock.reset(daoMock);
EasyMock.expect(daoMock.findByParentId(parentId)).andReturn(children);
EasyMock.replay(daoMock);
assertEquals(children, category.getSubcategories());
EasyMock.verify(daoMock);
}
Please note that like all internal-testing test cases, this one is pretty fragile to changes.