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.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.

Print This Post Print This Post

blog comments powered by Disqus
Creative Commons Attribution 3.0 Unported
Creative Commons Attribution 3.0 Unported