Tuesday 28 July 2009

Service Method Caching and Hibernate LazyInitializationExceptions

I ran into a slightly baffling problem using a combination of the Caching Spring Module (0.9) with Ehcache, Spring 2.5.6 and Hibernate 3.3.1 in a servlet application. After adding a Spring @Cacheable attribute to a method in a service object that returned a Hibernate data object I started to get the dreaded LazyInitializationException when iterating through a child collection in the JSP view whenever I hit the page a second time – and I was already using the OpenSessionInViewFilter!
@Cacheable(modelId=projectCachingModel)
@Transactional(readOnly = true)
public Project getProject(Integer projectId) {
return projectDao.load(projectId);
}
What gives? It seems that if the Project object is returned from the cache rather than from the DAO (which, incidentally, extends Spring HibernateDaoSupport) then the lazy loaded collections within the object can’t bind on to the Hibernate Session opened in the view, hence the LazyInitializationException. This sort of thing can be hard to test in a normal unit test. Luckily, it's possible to simulate what's going on in the view in a unit test using a few useful utilities in the Spring framework:
org.springframework.transaction.support.TransactionSynchronizationManager;
Now you can open and close sessions in much the same way that they would within the view as you navigate between pages.
// Get Hibernate session
SessionFactory sessionFactory = (SessionFactory)ctx.getBean("sessionFactory");
Session s = sessionFactory.openSession();

// Bind session to transaction synchroniser
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));

// Get Project (loads from database)
Project project = projectFacade.getProject(3);
  
// Close session
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
s = holder.getSession(); 
s.flush();
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(s);
  
// Start new session
Session s2 = sessionFactory.openSession();
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s2));

// Reload Project (from cache)
Project reload = projectFacade.findProject(3);
// Test lazy loading/initialisation
  
// Close session
holder = (SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
s2 = holder.getSession(); 
s2.flush();
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(s2);