Friday, 22 May 2009

How to Avoid ClassCastExceptions when using Hibernate Proxied Objects

Recently at work I was faced with an issue on the live system. A certain page within the application would only show the system's standard error message. After looking in the logs I discovered there was a ClassCastException being thrown, this was due to the fact that a lazily loaded List of objects retrieved by hibernate contained proxied objects. The entities that were proxied were using a single table inheritance strategy, so there is a base class that is subclassed several times. The entities once loaded were being sorted into separate lists that are typed according to the subclasses, they were being cast into the appropriate subclass but this cast was failing because instead of being an instance of say Animal it was an instance of Animal$$EnhancerByCGLIB$$10db1483 i.e. A Hibernate Proxy. Let me show you an example.

So lets say we have a base class that has an inheritance strategy set...

And then we had 2 subclasses of this Animal class, lets say, Cat and Dog...

As you can see the discriminator column is animal_type_id, which I have defined 2 to be Dog and 1 to be Cat. So now we should make sure we have some data in a database table called animals...

Right so to recreate a similar situation I will show you a unit test that will pass! What I am trying to do is to retrieve the list of animals and sort them in the java into separate lists, a list of dogs and a list of cats..

Now using the data in the database we want a list of 1 dog and a list of 2 cats. Here I'm being particularly evil and using instanceof to differentiate these. The assertEquals at the end of the test pass, even though the code looks like it should be adding items to these lists based on what is in the Adatabase. This is because animal is not an instanceof Cat or Dog but an instanceof Animal$$EnhancerByCGLIB$$10db1483.

So how do we solve this issue?

I first thought about using Hibernate.initialize(animal); which will force the animal class to become the actual object, not the proxy. I felt this approach to be dirty and it is really polluting code that shouldn't really know anything about hibernate.

So I did some googling and after reading the hibernate documentation and then this I set about using the Gang Of Four Visitor Pattern.

First we need a visitor interface that we can implement in your test...
Next we need is an interface for our Animal class to implement...
Now we make our Animal class I showed earlier implement the AnimalEntity...
And next implement the method in the subclasses..
All we need to do is implement the visitor in our test...
So instead of using instanceof or having to cast anything (which is what was happening in the live code I talked about) which can cause weird side effects and ClassCastExceptions, you can just use this visitor pattern. What happens is the test is implementing the AnimalEntityVisitor, so when animal.accept(this); is called, it calls the appropriate visit method on "this". In our case the visit method is just adding the object to a list. So I've updated the assertEquals calls to be what I now expect and the test passes.
Happy days, enjoy!
Technorati Tags: , , , , , ,

5 comments:

Bill Comer said...

That's excellent Andy. Very usefull.

Personnaly I would write the first test with the desired Assert statements in and leave them there until the tests passes.

சுபானு said...

Thanks you.. Excellent.. very useful..

0908LonniePettus said...
This comment has been removed by a blog administrator.
Janet L. Goldstein said...

Thanks from the software development team at the Johns Hopkins Center for Inherited Disease Research; this solved a hairy problem for us!

Peter Cipov said...

nice, nice, nice :) saves me a day