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…

package name.kayley.cglibexample;

import javax.persistence.*;

@Entity
@Table(name="animals")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    name="animal_type_id",
    discriminatorType=DiscriminatorType.INTEGER
)
public class Animal {

    @Id
    @GeneratedValue
    private Long id;
    
    @Column
    private String name;
    
    @Column
    private Integer age;
    
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

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


package name.kayley.cglibexample;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("1")
public class Cat extends Animal {
   // some cat specific attributes
}


package name.kayley.cglibexample;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("2")
public class Dog extends Animal {
   // some dog specific attributes
}

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…

SELECT id,animal_type_id,name,age FROM animals a LIMIT 0,1000

'id', 'animal_type_id', 'name', 'age'
1, 2, 'Scooby', 3
2, 1, 'Felix', 4
3, 1, 'Garfield', 4

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..

public void testCGLibInstanceOfError() throws Exception {

    List dogs = new ArrayList();
    List cats = new ArrayList();

    Iterator it = hibernateDao.createQuery("from Animal").iterate();

    while (it.hasNext()) {

        Animal animal = it.next();
        assertTrue(animal.getClass().getName().contains("Animal$$EnhancerByCGLIB$$"));
                    
        if (animal instanceof Cat) {
            cats.add((Cat) animal);
        } 
        else if (animal instanceof Dog) {
            dogs.add((Dog) animal);
        }
    }
    assertEquals(0, dogs.size());
    assertEquals(0, cats.size());
}

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 database. 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…

package name.kayley.cglibexample;

public interface AnimalEntityVisitor {
    void visit(Dog dog);
    void visit(Cat cat);
}

Next we need is an interface for our Animal class to implement…

package name.kayley.cglibexample;

public interface AnimalEntity {
    void accept(AnimalEntityVisitor visitor);
}

Now we make our Animal class I showed earlier implement the AnimalEntity


package name.kayley.cglibexample;

import javax.persistence.*;

@Entity
@Table(name="animals")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    name="animal_type_id",
    discriminatorType=DiscriminatorType.INTEGER
)
public abstract class Animal implements AnimalEntity {
   ...
   // see top of post
   ...
}

And next implement the method in the subclasses..


package name.kayley.cglibexample;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("1")
public class Cat extends Animal {
   // some cat specific attributes

   public void accept(AnimalEntityVisitor visitor) {
      visitor.visit(this);
   }
}


package name.kayley.cglibexample;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("2")
public class Dog extends Animal {
   // some dog specific attributes

   public void accept(AnimalEntityVisitor visitor) {
      visitor.visit(this);
   }
}

All we need to do is implement the visitor in our test…


public class MyTest 
    extends AbstractTransactionalDataSourceSpringContextTests 
    implements AnimalEntityVisitor {

        ...
    // some spring setup     
        ...

    private List dogs = new ArrayList();
    private List cats = new ArrayList();
    
    public void testCGLibUsingVisitor() throws Exception {

        Iterator it = hibernateDao.createQuery("from Animal").iterate();

        while (it.hasNext()) {

            Animal animal = it.next();

            assertTrue(animal.getClass().getName().contains("Animal$$EnhancerByCGLIB$$"));

            animal.accept(this);
            
        }
        assertEquals(1, dogs.size());
        assertEquals(2, cats.size());
    }

    public void visit(Dog dog) {
        dogs.add(dog);
    }

    public void visit(Cat cat) {
        cats.add(cat);
    }
}

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: , , , , , ,

Advertisements

How to Print Out Bind Variables in Java Prepared Statements.

Ever been frustrated by the fact that when you use (as you should do!) bind variables within a java prepared statement you cannot see the actual values that get put into those bind variables? I know I have. For example if you had a class that had some JDBC calls that did the following….

Class.forName("com.mysql.jdbc.Driver");

connection = DriverManager.getConnection("jdbc:mysql://localhost/message_board", "root", "");

statement = connection.prepareStatement("select * from messages where id = ?");
statement.setLong(1,1L);

resultSet = statement.executeQuery();

There is no nice out the box way to find out what the ?’s will be resolved to. I’ve always wanted a method on PreparedStatement that would address this, but alas there still is none. Traditionally I would have done something like the following…


long id = 1L;
String sql = "select * from messages where id = ?"
logger.info(sql + "; ?1=" + id);

statement = connection.prepareStatement(sql);
statement.setLong(1,id);

Or write your own convoluted method for replacing each ? with the value which is prone to bugs and not being database agnostic.

When using an old jdbc based in-house framework this really wasn’t an issue for me, as we had such a method that was prone to bugs but for the most part it worked ok. Since then the work I have been doing has been much more hibernate based. This is where the issue came for me, you can turn on sql logging either by configuring your favourite logging framework to do so, or by turning on the hibernate.show_sql property, which will give you the following output…


select message0_.id as id2_, message0_.email_address as email2_2_, message0_.inserted_at as inserted3_2_, message0_.remote_host as remote4_2_, message0_.text as text2_ from messages message0_ where message0_.id=?

This sql was generated from the following hql

from Message where id = ?

Now apart from this sql being particularly hideous to look at, the question mark is still there, we have no way of possibly knowing what those bind variables were without printing them out at the time the hql is executed.

Enter p6spy.

I did a bit of googling about and found other people had the same issue. And there is an answer to this problem, and its called p6spy. P6spy is a jdbc driver that is a proxy for your real driver. I’ve only just started to use it, I believe it is capable of many cool things, but the one that I am interested in allows you to print out sql from prepared statements with – yep, you guessed it – the bind variables substituted for the real values.

You can download it from http://www.p6spy.com, it must be fairly stable as the last release was in 2003 (I’m so annoyed that I didn’t know about this earlier). There seems to be a little problem with their website as when you click download you get an error while trying to fill in or ignore their survey. Don’t panic, once you’ve ignored the survey go back to the main page and click download again and the files will be there waiting for you to download.

So how do we configure this little beauty? Well the download is p6spy-install.zip, unzip this somewhere safe (and keep in mind that all the files are in the root of the zip file, so you might want to create a directory to unzip it to first). The files we’re interested in are p6spy.jar and spy.properties. Copy the jar file into the lib folder of your project and copy the spy.properties file into somewhere that will be on the classpath.

If you look inside spy.properties you will see that it’s really very well documented and tells you all you really need to know on how to configure it. Although one thing to note in there is that they have said that the real driver you configure in your application should be set to com.p6spy.engine.P6SpyDriver which is actually incorrect, it should be com.p6spy.engine.spy.P6SpyDriver, the first one doesn’t exist and you will get a ClassNotFoundException.

So what I did… In my spy.properties I set the realdriver…

# mysql Connector/J driver
realdriver=com.mysql.jdbc.Driver

(note by default the open source mysql driver is uncommented which was a gotcha for me at first, so I commented it out, and uncommented the above)

Next I altered my code in the first example to the following…

Class.forName("com.p6spy.engine.spy.P6SpyDriver");

connection = DriverManager.getConnection("jdbc:mysql://localhost/message_board", "root", "");

statement = connection.prepareStatement("select * from messages where id = ?");
statement.setLong(1,1L);

resultSet = statement.executeQuery();

And lo and behold in the root of my eclipse project on the file system a file called spy.log has appeared. and in it the following…

1217374504582|6|0|statement|select * from messages where id = ?|select * from messages where id = 1
1217374504595|-1||resultset|select * from messages where id = 1|email_address = myemail@nospam.com, id = 1, inserted_at = 2008-07-29 00:00:00.0, remote_host = localhost, text = Thank you for showing me the wonders of p6spy

There is lots of other stuff in the log file as well but as you can see its very handy, as it shows you the statement with question marks and with the real values substituted, and also shows you the results set which is passed back 🙂 Beautiful lol.

And the hql of

from Message where id = ?

is output as:


1217374913350|5|2|statement|select message0_.id as id2_, message0_.email_address as email2_2_, message0_.inserted_at as inserted3_2_, message0_.remote_host as remote4_2_, message0_.text as text2_ from messages message0_ where message0_.id=?|select message0_.id as id2_, message0_.email_address as email2_2_, message0_.inserted_at as inserted3_2_, message0_.remote_host as remote4_2_, message0_.text as text2_ from messages message0_ where message0_.id=1
1217374913369|-1||resultset|select message0_.id as id2_, message0_.email_address as email2_2_, message0_.inserted_at as inserted3_2_, message0_.remote_host as remote4_2_, message0_.text as text2_ from messages message0_ where message0_.id=1|email2_2_ = myemail@nospam.com, remote4_2_ = localhost

Still ugly but at least I can now see what the bind variables are going to be 🙂

You can also configure it so it comes out in your normal logging file, the spy.properties file details how you do that.

Oh how I wish I’d have known this 4 years ago.

Technorati Tags: , , , , ,,,