Due to popular demand, I will post a very short version of my Lazy Loading article:
Why?
Because this is bad:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 |
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:
1 |
System.out.println(dao.get(1).getParent().getChildSum()); |
Where getChildSum() is:
1 2 3 4 5 6 7 |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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)
1 2 3 4 5 6 7 8 |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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.