Hi there,
Can someone please explain me the following.
I'm creating a Hibernate 3.1 java project with 2 mapped classes, Child and Parent. Child derives from Parent. Parent derives from GrandPa, an abstract base class. The two classes map to respective tables called child and parent, on a vanilla mysql 4.1.
If I execute a query by example finder on the Child, everything works nicely. If I execute a finder on the Parent, I get "ClassCastException".
Debugging the Hibernate code, one finds that org.hibernate.impl.SessionImpl has loaded both Parent and Child as implementors of Parent, but Parent is not of type Child (org.hibernate.tuple.PojoInstantiator, lines 110 and 111). Note that curiously, I'm executing a finder on the Parent at this point.
One also notices that the debugger goes twice into Example.getEntityMode(), and only throws up on the second turn.
The following did not help:
- migrated back to Hibernate 3.0: same issue
- added entity-name="..." attribute in <class> element in hbm file, as suggested in the docs chapter "Mapping a class more than once"
The following did help:
- break inheritance between Parent and Child, such that Parent and Child become siblings in the class hierarchy
- in the hbm files of both classes, set polymorphism="explicit"
I'll gladly send the Eclipse (IBM RAD 6.0) project to anyone who wants to make an attempt at reproducing the behaviour.
Somehow I get the impression that this sort of behaviour is either not very well documented, or represents a bug.
Thanks in advance,
Ulrich Kroener
Hibernate version: 3.1
Mapping documents:
=============
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/mspsample</property>
<property name="connection.username">root</property>
<property name="connection.password">yeahright</property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">true</property><!-- Echo all executed SQL to stdout -->
<mapping resource="be/abvv/test/hiberissue/Parent.hbm.xml"/>
<mapping resource="be/abvv/test/hiberissue/Child.hbm.xml"/>
</session-factory>
</hibernate-configuration>
=============
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="be.abvv.test.hiberissue.Parent" table="parent">
<composite-id name="id" class="be.abvv.test.hiberissue.ParentKey">
<key-property name="nn" type="long">
<column name="nn" />
</key-property>
</composite-id>
<property name="foo" type="long">
<column name="foo" not-null="true" />
</property>
</class>
</hibernate-mapping>
=============
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="be.abvv.test.hiberissue.Child" table="Child">
<composite-id name="id" class="be.abvv.test.hiberissue.ParentKey">
<key-property name="nn" type="long">
<column name="nn" />
</key-property>
</composite-id>
<property name="foo" type="long">
<column name="foo" not-null="true" />
</property>
<property name="bar" type="long">
<column name="bar" not-null="true" />
</property>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():
/*
*
*/
package be.abvv.test.hiberissue;
import java.util.List;
import junit.framework.TestCase;
/**
*
*/
public class ParentTest extends TestCase {
public void testFind() {
String msgPrefix = null;
Parent found = null;
List foundList = null;
Parent crit = new Parent();
crit.setFoo(1L);
// find
msgPrefix = "find failed: ";
try {
foundList = Parent.find(crit);
assertTrue( msgPrefix + "null returned", foundList != null);
assertTrue( msgPrefix + "zero items found", !foundList.isEmpty());
} catch (RuntimeException e) {
e.printStackTrace();
assertTrue(msgPrefix + e.getMessage(), false);
} finally {
HibernateUtil.closeAndCommitAllSessions(); // close session in any case
}
}
}
====================
package be.abvv.test.hiberissue;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
/**
*/
public class Child extends Parent {
public Child() {
}
public Child(ParentKey id) {
super(id);
}
private long bar;
public long getBar() {
return this.bar;
}
public void setBar(long linr) {
this.bar = linr;
}
public static Child findById(Child searchData) {
try {
return (Child)HibernateUtil.getCurrentSession().get(searchData.getClass(),searchData.getIdentifier());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to find"+ searchData.getName()+" with id:"+searchData.getIdentifier()+" error:"+e+"-"+e.getMessage());
}
}
/..
public static final String name = "Child";
String getName() {
return name;
}
Serializable getIdentifier() {
return new ParentKey(getId());
}
}
========================
package be.abvv.test.hiberissue;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
/**
*/
public class Parent extends GrandPa {
public Parent() {
}
public Parent(ParentKey id) {
this.id = id;
}
private ParentKey id;
private long foo;
public ParentKey getId() {
return this.id;
}
public void setId(ParentKey id) {
this.id = id;
}
public long getFoo() {
return this.foo;
}
public void setFoo(long linr) {
this.foo = linr;
}
public static List find(Parent searchData) {
Criteria crit = HibernateUtil.getCurrentSession().createCriteria(searchData.getClass());
Example exampleQBE = Example.create(searchData);
exampleQBE.ignoreCase(); // for strings, ignore case
exampleQBE.enableLike(MatchMode.ANYWHERE); // for strings, this will give "like '%foo%'"
exampleQBE.excludeZeroes(); // do not add java nulls as search criteria
exampleQBE.excludeProperty("id"); // You need to exclude the PROPERTY of the primary key,
crit.add(exampleQBE);
try {
return crit.list();
} catch (HibernateException e) {
throw new RuntimeException("Unable to perform find:"+e+"-"+e.getMessage());
}
}
// ...
public static final String name = "Parent";
String getName() {
return name;
}
Serializable getIdentifier() {
return new ParentKey(getId());
}
}
==========================
package be.abvv.test.hiberissue;
import java.io.Serializable;
/**
* ParentKey Composite primary key class for Parent
*/
public class ParentKey implements Serializable {
private long nn;
public ParentKey() {
}
public ParentKey(ParentKey id) {
this.setNn(id.getNn());
}
public ParentKey(long nn) {
this.nn = nn;
}
public long getNn() {
return this.nn;
}
public void setNn(long nn) {
this.nn = nn;
}
// ...
}
==========================
/*
*
*/
package be.abvv.test.hiberissue;
import java.io.Serializable;
import org.hibernate.HibernateException;
/**
*
*/
public abstract class GrandPa {
abstract String getName();
abstract Serializable getIdentifier();
public void save() {
try {
HibernateUtil.getCurrentSession().save(this);
} catch (HibernateException e) {
throw new RuntimeException("Unable to save "+getName()+" "+e +"/n"+e.getMessage());
}
}
// .. public void update() , delete()
}
==============================================
package be.abvv.test.hiberissue;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
/**
* Utility class for hibernate. Enables configuration and session caching. By
* using the thread local pattern it ensures that session are not shared across
* threads.
*/
public class HibernateUtil {
/**
* Hibernate config file. Is static and shared across threads
*/
public static final String CONFIG_FILE = "/hibernate.cfg.xml";
/**
* Session factory holds the configuration data loaded by the config file
*/
private static SessionFactory internalSessionFactory;
/**
* Static variable linked to a thread holds the transaction
*/
private static final ThreadLocal transactionCache = new ThreadLocal();
/**
* Static variable linked to the thread holds the session.
*/
private static final ThreadLocal sessionCache = new ThreadLocal();
/**
* gets a hibernate session using the class defaults to initializes the
* session. When creating a session a transaction will also be started. To close
* the session and commit the transaction use <code>closeAndCommitAllSessions</code> when
* needed you can also use <code>closeAndRollbackAllSessions</code>
*
* @return Session to the database
*/
public static Session getCurrentSession() {
return getCurrentSession(internalSessionFactory);
}
/**
* Returns a hibernate session. First checks the cache, if nothing is found
* in the cache creates a new session. When creating a session a transaction will also be started. To close
* the session and commit the transaction use <code>closeAndCommitAllSessions</code> when
* needed you can also use <code>closeAndRollbackAllSessions</code>
*
* @param sessionFactory
* SessionFacrtory to be used when creating the session.
* @return Session to the database
*/
public static Session getCurrentSession(SessionFactory sessionFactory) {
try {
//Retrieves the session from the "thread cache"
Session s = (Session) sessionCache.get();
if (s == null || !s.isOpen()) {
//Checks if the factory was initialized
if (sessionFactory == null) {
//Initializes the factory with the configuration file
sessionFactory = new Configuration().configure(CONFIG_FILE).buildSessionFactory();
internalSessionFactory=sessionFactory;
}
//Cache was empty or session was closed.Creating new session
s = sessionFactory.openSession();
Transaction t = s.beginTransaction();
//Stores the session and the transaction in the cache
sessionCache.set(s);
transactionCache.set(t);
}
return s;
} catch (Exception e) {
throw new RuntimeException("Unable to retrieve Hibernate Session:" + e.getMessage());
}
}
}
Full stack trace of any exception that occurs:
java.lang.ClassCastException: be.abvv.test.hiberissue.Parent
at org.hibernate.criterion.Example.getEntityMode(Example.java:247)
at org.hibernate.criterion.Example.toSqlString(Example.java:177)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:316)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:86)
at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:67)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1473)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:298)
at be.abvv.test.hiberissue.Parent.find(Parent.java:87)
at be.abvv.test.hiberissue.ParentTest.testFind(ParentTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:85)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:58)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:60)
at java.lang.reflect.Method.invoke(Method.java:391)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:436)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:311)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Name and version of the database you are using: MySQL 4.1, mysql Ver 14.7 Distrib 4.1.15, for Win32 (ia32)
The generated SQL (show_sql=true): N/A
Debug level Hibernate log excerpt: WARN
|