How to live with hibernate proxies
April 2nd, 2008 at 17:26 by protokollfuehrerStumbled over the fucking hibernate-CGlib-Proxies again. So here is what I found out and what helped me a lot:
If you load an object via the load()-method hibernate returns a proxy by default. As long as you don’t access any primitive property this thing won’t materialize. No prob, you might think and in most circumstances it isn’t. But consider this example:
class Plan {
// Cascade-type all, fetch lazy
private Plan lastVersion;
private boolean active
public Plan(Plan lastVersion){
if (lastVersion != null){
lastVersion.active = false;
}
this.active = true;
}
}
I don’t want to expose a public setter here to ensure the integrity of my versioning. Now the fuck-up:
Let’s say I get the current Plan from my Client and want to save it, the following code seems pretty straight forward:
// transactionmanaged service-method
Plan lastVersion = getHibernateTemplate().load(Plan.class, 4)
Plan currentPlan = new Plan(lastVersion);
getHibernateTemplate().saveOrUpdate(currentPlan);
BUT (as you might have guessed) this doesn’t work. At the end I do have two entries in my db both marked as active. Why that? lastVersion is a cglib-Proxy and hibernate does not recognize any changes that are not done by the setter. A possible workaround would be a private setter in the plan class, but somehow not even this is recognized by hibernate. So what are our options? One is to change the fetchmode of the lastversion. But doing so means sucking all previous versions from db when loading. Same happens when you mark the class @Proxy(lazy=false) as its a recursive structure. Played around with access types a while but that doesn’t have any influence. Finally I found out, that if you work with the “real” object instead of the proxy, it all works fine. To do so, you have to use the method get() instead of load(). Get() makes the db call instantly and returns a materialized object. In difference to the eager-fetchmode, all containing subtypes remain proxies.
That all leads to the questions, how we can make a clean separation between the ORM-Layer and our business-objects with hibernate. It’s just not transparent, when we got hibernate-specific-behaving objects flyin’ around in our domain-layer. And well, I don’t really have an answer on that…
April 16th, 2008 at 17:00
http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/
April 16th, 2008 at 17:00
http://cwmaier.blogspot.com/2007/07/liskov-substitution-principle-equals.html
April 16th, 2008 at 17:53
Why does hib not recognize private setter? Simple answer: The CGLib-Proxy cannot override this method.
April 16th, 2008 at 18:24
Another selfmade comment:
All these problems lead to another BIG BIG problem: How do we test an application which uses hibernate? We did not find a really good answer yet. Our workaround is a H2-database. So we don’t mock the DAOs but write getPersistentBlaBla()-methods which write to DB and return the loaded proxy. Pain in the ass…..
May 1st, 2008 at 13:07
how about a package-access setter? What do you mean a private setter is not recognized by Hibernate – it behaves as if you used a direct field access?
May 5th, 2008 at 22:40
package access is a possibility, but I want a real encapsulation. Hibernate works with CGlib-Proxies. These proxies are dynamic subclasses: If you have a proxy, you have in fact a subclass. This subclass overwrites the methods of the parent class and initialisizes the “real” object when such a method is called. As you cannot override private methods, calls won’t be recognized.
Field access is something else: That only defines how hibernate accesses the mapped objects.
May 7th, 2008 at 15:56
[...] they handle bugs. It’s their inconsistence with these two totally separated query-languages. It’s their proxy-fuckup. And i’m not alone with that [...]