-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: Query returns cached proxies of wrong class. Bug?
PostPosted: Tue Feb 21, 2006 12:29 am 
Newbie

Joined: Sat Feb 18, 2006 5:30 am
Posts: 17
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 definition

Code:
<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 sample

Code:
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?


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 03, 2006 1:37 am 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
I don't think that there's a bug here. This example highlights the dangers of proxying polymorphic objects. session.get(A.class, id) will return a proxy for A.class, not for any of its implementations. This is expected, though it's obvious that it's not what you were looking for. This is why I don't use joined-subclass, and intend to never use it.

You can always (?) map joined subclasses directly to the implementation classes, and completely remove the common superclass from the mapping. This solution has never let me down.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 03, 2006 1:58 am 
Newbie

Joined: Sat Feb 18, 2006 5:30 am
Posts: 17
tenwit wrote:
You can always (?) map joined subclasses directly to the implementation classes, and completely remove the common superclass from the mapping. This solution has never let me down.


We can't. Our legacy database schema uses common "base" table for the most of entities and also used to contain herarchical relationships (reference to parent, especially)

Our solution is lazy-initialized references, proxified using bytecode instrumentation.

Ofcourse, we plan to clean-up schema and get rid of common base entity, but for now it works.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 03, 2006 2:12 am 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
I'd say that you probably can. I'm not suggesting that you change your schema or your java data model, just your mapping. Don't map the super class, just map the child classes directly. Make use of <class where="">.

_________________
Code tags are your friend. Know them and use them.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.