Summary: If proxy for base class of inheritance hierarchy is cached in session's persistence context, it is returned from mathing HQL query, despite of presence of actual child class instance.
Suppose, we have an inheritance hierarchy. Classes B1 and B2 extends class A and class C contains association with class A. Class A is abstract, so there is always corresponding entry in either B1 or B2 tables. These classes mapped to database using table per class strategy (using joined-subclass element).
Database schema
Code:
create table A(
id integer primary key,
prop1 integer,
prop2 integer );
create table B(
id integer primary key );
create table C(
id integer primary key,
a_id integer );
Mapping definitionCode:
<class name="A" table="A">
<id ... />
<property name="prop1" />
<property name="prop2" />
</class>
<joined-subclass name="B" extends="A" table="B">
<key ... />
</joined-subclass>
<class name="C" table="C">
<id ... />
<many-to-one name="a" class="A" column="a_id" lazy="true"/>
</class>
Code sampleCode:
C c = ( C ) session.get( C.class, new Integer( 123 ) );
// lets assume, that
assert c.getA() instanceof B1;
assert c.getA().getProp1() == 1;
assert c.getA().getProp2() == 2;
// same object as in c.getA() is expected
A a = ( A ) session
.createQuery( "from A where prop1 = 1 and prop2 = 2" )
.uniqueResult();
// according to above, this assertion must always pass
assert a instanceof B1 : a.getClass() + " extends " + a.getClass().getSuperclass();
I recieve assertion failed error at the last line with message
"class A$$EnhancerByCGLIB$$1d1e90ff extends class A". This happens only when the first line is executed.
I dig into Hibernate internals and found, that when instance of class C is loaded, its associations is resolved and proxy for A (of syntetic class A$$EnhancerByCGLIB$$1d1e90ff) is cached inside
StatefulPersistenceContext. Then, when query is executed and instance of class B1 is loaded from database, it is replaced by its proxy (method
Loader.getRowFromResultSet(..). If the first query is not executed, there is no cached proxy of B1 instance, so loaded instance is returned as is. In other case, loaded instance is replaced by it's proxy, which has type A$$EnhancerByCGLIB$$1d1e90ff, instead of B1 (by design, AFAIK). The problem is that EntityPersister of base class (class A) is used when narrowing proxy, so actually no narrowing takes place.
Proposed solution is to change line (Loader.java:586 in Hibernate 3.1.2)
Code:
Object proxy = session.getPersistenceContext().proxyFor( persisters[i], keys[i], entity );
on
Code:
Object proxy = session.getPersistenceContext().proxyFor(
session.getEntityPersister( null, entity ), keys[i], entity );
which will allow narrowing proxy to correct type. I've tested this code, but not extensively.
The problem is that identity (==) operation will be broken (c.getA() != a in code above). Maybe add hint of flag to Query interface, to allow such behavior? Anyway, is this expected way to perform queries or bug?