-->
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.  [ 5 posts ] 
Author Message
 Post subject: "De-proxying" isn't leading to actual subclass
PostPosted: Mon Dec 26, 2005 1:36 pm 
Newbie

Joined: Mon Dec 26, 2005 12:00 pm
Posts: 8
Hello everyone,

I'm using Hibernate 3.1. I have a recurrent problem, albeit maybe quite simple, about retrieval of subclassed entities.

I'll explain using a clean example: Suppose I have four classes -- ClassA, ClassB, ClassC, and ClassCChild, where the latter is a subclass of ClassC. ClassA has a property of type ClassB, which in turn has a property of type ClassC (both are mapped via "many-to-one" relationships):

Code:
public class ClassA {
    private ClassB classB;
    // getter/setter follows...
}

public class ClassB {
    private ClassC classC;
    // getter/setter follows...
}


Mapping looks like:

Code:
<class name="ClassA" ...>
    ...
    <many-to-one
        name="classB"
        class="ClassB"
        column="IDCLASSB"/>
</class>


...and similar to ClassB.classC. The ClassCChild inheritance is represented by the "Table per Hierarchy" model, thus having a discriminator value.

Now let me load an instance of ClassA and obtain the referred ClassC instance:

Code:
ClassA classA = (ClassA) session.get(ClassA.class, new Integer(1));
ClassC classC = classA.getClassB().getClassC();


According to data in my database, classC should hold a ClassCChild instance. This is in fact evidenced by calling Hibernate.getClass():

Code:
System.out.println(Hibernate.getClass(classA.getClassB().getClassC()));
// returns "class ClassCChild", as expected


However, I can't typecast classC to ClassCChild:

Code:
ClassCChild child = (ClassCChild) classC;
// yelds a java.lang.ClassCastException exception


I understand that this is an issue of how CGLIB handles proxied objects -- when I get() a ClassA instance, it's impossible to know in advance what is the type of classA.getClassB().getClassC(), so it's mapped to a general ClassC proxy. But, what to do now? The only solution that I found so far is to ask explicitely for the ClassCChild instance...

Code:
ClassCChild child = (ClassCChild) session.get(ClassCChild.class, classC.getId());


...which I find to be rather ugly, since Hibernate+CGLIB should (and have enough information to) give me an instance of the correct class. (Anyway, classA.getClassB().getClassC() will always point to a "pure" ClassC instance or proxy.) Or I could issue a "deep-outer-join-query" to avoid proxies and obtain the correct class from the beginning -- but this isn't good for me, since I have a quite "fat" database (both in rows and in columns) and I want to retrieve data only when it's strictly necessary.

Now, can anyone give me a hint?

_________________
Best regards,
Vilar Camara Neto

(please, don't forget to vote...)


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 26, 2005 7:29 pm 
Newbie

Joined: Mon Dec 26, 2005 7:02 pm
Posts: 3
This is actually a "bug/design feature" in cglib. I ran into the same problem in my application and couldn't really come up with an elegant workaround. Other posters will tell you to create an interface that implements all the methods in all the subclasses in your heirarchy, effectively flattening your API into a single interface.

To work around this design feature I cooked up an ugly, but servicable workaround...


Code:
import org.hibernate.proxy.LazyInitializer;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;



public class ProxyUtils {
   
   public static Object getProxyTarget(Object domain) {
      if (domain instanceof Factory) {
         Callback[] callbacks = ((Factory)domain).getCallbacks();
         for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof LazyInitializer) {
               return  ((LazyInitializer)callbacks[i]).getImplementation();
            }
         }
         return null;
      }
      else {
         return domain;
      }
   }

}


This is terrible and I apologize to you and your family for even suggesting it, but it works for me.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 7:21 am 
Newbie

Joined: Mon Dec 26, 2005 12:00 pm
Posts: 8
Thank you, thelifter! Indeed, it solves the problem in a "quasi-elegant" way.

Now a complimentary question:

Can I use it inside getters/setters without actually breaking Hibernate/CGLIB functionality? E.g. (using my original example):

Code:
public class ClassB {
    private ClassC classC;

    public ClassC getClassC() {
        return classC;
    }
    public void setClassC(ClassC classC) {
        this.classC = (ClassC) ProxyUtils.getProxyTarget(classC);
    }
}


I fear that this may lead Hibernate to think that instances of ClassB are always dirty. Or maybe some other side effect that I can't imagine right now.

Any comments?

_________________
Best regards,
Vilar Camara Neto

(please, don't forget to vote...)


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 27, 2005 9:39 am 
Newbie

Joined: Mon Dec 26, 2005 7:02 pm
Posts: 3
I having a feeling that be pushing our luck, but I've never tried doing it that way, so feel free to give it a whirl and let us all know how it turns out.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 6:30 am 
Newbie

Joined: Mon Dec 26, 2005 12:00 pm
Posts: 8
Just to tell everyone: I've implemented the setter as seen in my last post, and I didn't have any problems so far.

This is expected, as stated everywhere: Hibernate detects changes in values by comparison, not by object identity (unless the property is a collection, which is not the case). There's even an example in Hibernate in Action, p. 74, where a getter returns new instances of Strings.

_________________
Best regards,
Vilar Camara Neto

(please, don't forget to vote...)


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 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.