Sunday, April 03, 2011

Hibernate WrongClassException : discriminator force = 'true'

As the java docs of the org.hibernate.WrongClassException says :

"Thrown when 'Session.load()' selects a row with the given primary key (identifier value) but the row's discriminator value specifies a subclass that is not assignable to the class requested by the user."

This could happen under several different scenarios. Here I would discuss a particular scenario in which this occurred and it was a bit tricky to identify the solution of this problem.

Following Table-Per-Class hierarchy inheritance mapping an application had a scenario which is described below.

Consider the following data model.

Listing 1
class Shop {
Collection deskTops;
Collection lapTops;
..
}

class DeskTop extends AbstractComputer {
...
}

class Laptop extends AbstractComputer {
...
}

Hibernate Mapping :

<class abstract="true" discriminator-value="null" name="AbstractComputer" table="COMPUTER">
<discriminator column="TYPE" type="big_integer"/>
...
...
</class>

<subclass discriminator-value="1" extends="AbstractComputer" name="DeskTop">
...
...
</subclass>

<subclass discriminator-value="2" extends="AbstractComputer" name="Laptop">
...
...
</subclass>

Consider a shop selling X laptops and Y desktops.
Assuming the shop object has a lazy access to the collection of desktops and laptops, the following code with burst with an exception.

Listing 2
1. shop = (Shop) shopDAO.readById(123);
2. Collection deskTops = shop.getDeskTops();
3. deskTops.iterator().hasNext();

The exception would occur at line number 3 and would be similar to :

Listing 3
org.hibernate.WrongClassException: Object with id: 1243 was not of the specified subclass: DeskTop (loaded object was of wrong class class Laptop) 
 at org.hibernate.loader.Loader.instanceAlreadyLoaded(Loader.java:1307)
 at org.hibernate.loader.Loader.getRow(Loader.java:1260)
 at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:619)
 at org.hibernate.loader.Loader.doQuery(Loader.java:745)
 at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:270)
 at org.hibernate.loader.Loader.loadCollection(Loader.java:2082)
 at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:62)
 at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:628)
 at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:83)
 at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1853)
 at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:369)
 at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
 at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:272)


This happens because hibernate does not tries to distinguish between the Desktops and Laptops, while fetching the collection deskTops of a given shop. It gets all the AbstractComputers in the database (as illustrated at listing 2 in line 3) when you try to access the deskTops collection and hence inflate this collection. While doing so, it tries to build a DeskTop objects from the returned rows, these rows could contain a Laptop as well, and hence the WrongClassException.

Examining the fired (at listing 2 in line 3) SQL query below :

SELECT deskTops0_.SHOP AS SHOP242_1_,
  deskTops0_.ID             AS ID1_,
  ....
  ....
FROM PUBLIC.COMPUTER deskTops0_
WHERE deskTops0_.SHOP=?

It shows in this case when the deskTops collection is accessed, the SQL fired would fetch the complete list of all computers for a given shop. It would not differentiate if the given computer is a desktop or not. Hence the wrongClassException would be thrown while inflating the deskTops collection.

In order to solve this problem, I changed the force property of discriminator attribute, from the default value of 'false' to 'true'. hence the modified hbm file for the Computer object looked like :

<class name="AbstractComputer" table="COMPUTER" abstract="true" discriminator-value="null">
<discriminator column="TYPE" type="big_integer" force="true"/>

...
...
</class>

The property force=true will ensure that every query fired for the Computer objects would have discriminator taken into account. Hence while accessing the same deskTop collection as above, now the fired query would be somthing like :

SELECT deskTops0_.SHOP AS SHOP242_1_,
  deskTops0_.ID             AS ID1_,
  ....
  ....
FROM PUBLIC.COMPUTER deskTops0_
WHERE deskTops0_.TYPE=1
AND deskTops0_.SHOP=?

As you can see it included the disciminator, which is 'type' in this case. The value 1 is for the subclass Desktop of Abstract Computers. Hence with this query only the data of type Desktop ois returned and WrongClassException is avoided.