With Java Reflection we can force Hibernate to instantiate lazily-loaded (aka proxy) member objects before we attempt to save the containing entity.
Yesterday I posted my Spring + Hibernate + Cloud SQL sample project code to help other developers get up and running with Hibernate and App Engine.
Today I had to make some DB changes and slightly rearchitected my code. That uncovered a problem. I'm using Spring's OpenSessionInViewFilter to support lazy loading in the view layer. It's dang convenient. And it's currently configured for singleSession=false. That means that it'll use a read-only Session for gets but a separate save/update Session for writes.
Calling getHibernateTemplate().save() had been working fine until my code reshuffling exposed a problem. If an entity has any member objects that have not yet been initialized (due to lazy loading), the save() call will throw a session conflict exception. Specifically:
org.springframework.orm.hibernate3.HibernateSystemException: illegally attempted to associate a proxy with two open Sessions
Here's a simple example:
The problem is that the read-only Session created the lazy-loading proxy Owner within oldCat but hasn't been asked to initialize the proxy yet. Then the save/update Session gets the newCat and tries to access its Owner and now both Sessions are trying to handle the non-initialized proxy, thus the "two open Sessions" complaint.
So folks will generally do something lame like this to get around it:
I'm not judging. I've done it in the past too. But it's clearly a crappy solution. If that chunk of code doesn't need to access any fields in Owner, it shouldn't have to just to make the DAO/ORM layers happy.
So here's my new solution: programmatically search for non-initialized proxies and call getHibernateTemplate().initialize() on them before we try to save.
This requires Reflection to inspect the Cat and look for fields that might need initialization.
Unfortunately my DAO doesn't have permission to access Cat's private member variables. So instead we scan for the public getters and look at their return types. If the return type is a domain object that is mapped by Hibernate, then we "get" it and initialize as needed.
How do we know it's mapped by Hibernate? I'm just filtering on the return type's package name; I only care about DTOs from com.essaytagger.model. The proxies that need initialization are easy to spot--they return true to (obj instanceof HibernateProxy) tests.
Here's the new version of my _GenericDaoHibernateImpl's save() method (my GenericDAO approach is kind of complicated. Track back through the Spring+Hibernate+CloudSQL post first and then see the more in-depth discussion of the original Objectify-based version that was adapted from David Chandler):
This GenericDAO work is getting me more and more into Java Reflection. It's pretty dang cool! But I'm not completely confident in this approach just yet. We'll see if any other odd use cases popup before we call this a win.
What do you think? See any problems down the road?