-->
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.  [ 18 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: ClassCastException cglib lazy loading
PostPosted: Wed Aug 02, 2006 3:29 pm 
Newbie

Joined: Wed Aug 02, 2006 3:08 pm
Posts: 2
Location: NY
I have using Hibernate 3.1.3. I have a mapping like this:
Code:
   <class name="Contact" abstract="true">
      <id name="id" column="CONTACT_ID">
         <generator class="native"/>
      </id>         
   </class>

   <joined-subclass name="Address" extends="Contact">
      <key column="CONTACT_ID"/>
      <property name="address"/>
      <property name="city"/>
      <property name="state"/>
   </joined-subclass>

   <class name="Shipment">
      <id name="id">
         <generator class="native"/>
      </id>
      <many-to-one name="sendTo"/>
   </class>

Basically I have Address extending from Contact and Shipment has a many-to-one to Contact. I get a ClassCastException when I do this:
Code:
        Shipment shipment = ...;
        Address addr = (Address) shipment.getSendTo();

shipment.getSendTo() returns a Contact$$EnhancerByCGLIB$$7e7de4eb object. I can cast it to Contact, but not Address.

I can fix it by adding lazy="false" to the many-to-one "sendTo", but is this the right thing to do? Another words for ALL many-to-one objects that references objects that are subclassed, I need to add lazy="false"?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 03, 2006 1:29 am 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
This will work, too:
Code:
Hibernate.initialize(shipment.getSendTo());
Address addr = (Address) shipment.getSendTo();
If you had any non-ID properties in Contact, you could also access one of them to deproxy the contact, turning it into a real Address object.

What class have you specified in the many-to-one mapping? You've dropped that. I think that you have to specify Contact, but try specifying Address, if appropriate. That would work, if it's allowed.

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


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 03, 2006 9:30 am 
Newbie

Joined: Wed Aug 02, 2006 3:08 pm
Posts: 2
Location: NY
Thanks for the quick reply. Yes, Shipment.getSendTo() returns a Contact. We want to keep it like that so we could also use it for electronic delivery where getSendTo() returns an EmailAddress object.

We don't want to polute the code with calls to Hibernate. I guess we will live with lazy="false". It's not a big deal. I just thought there might a better solution.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 03, 2006 5:06 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
Or you could write a utility method that "casts" a contact to an address: if you ever drop hibernate, you'd only have to modify the utility method:
Code:
public Address castToAddressOrNull(Contant ctct)
{
  Hibernate.initialize(ctct);
  if (ctct instanceof Address)
  {
    return (Address) ctct;
  }
  return null;
}
Address addr = castToAddressOrNull(shipment.getSendTo());

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


Top
 Profile  
 
 Post subject:
PostPosted: Fri Sep 22, 2006 5:14 am 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
Hello,

The problem is that the CGLIB proxy class is a direct descendent of the type of the one end of the many-to-one relationship so you will not be able to cast to the subtype while the object is being proxied.

The neatest solution to this is to employ the visitor pattern. This will keep your code free of hibernate clutter and will also allow you to continue to use lazy loading.

Define a visitor interface with a visit<classname>(Subclass classname) method for each concrete subclass in your hierarchy, i.e. in your case:

Code:
public interface ContactVisitor {
    void visitAddress(final Address address);
}


Then define an accept(VisitorInterfaceName visitor) method in the base class of your hierarchy, in your case Contact. You can make this abstract if your base class is abstract or give it an empty implementation, i.e:

Code:
public abstract class Contact {
    public abstract void accept(ContactVisitor visitor);

    ...
}


Then in your concrete subclasses provide an implementation of accept that simply calls visitor.visit<classname>(this), e.g. in your Address class you would have:

Code:
public class Address extends Contact {
     public void accept(ContactVisitor visitor) {
         visitor.visitAddress(this);
     }
}


Now I guess the whole reason why you want to have an association to the base class is to have some different behaviour depending on the run-time type of the Contact returned from getSendTo(), probably calling a method that is only defined in the subclass. This is where you use your visitor pattern by implementing your visitor interface for the specific behaviour you want then calling accept on your Contact. The quickest way for simple behaviour is to use an anonymous class:

Code:
Shipment shipment = ...;
    Contact contact = shipment.getSendTo();
      
    contact .accept(new ContactVisitor() {

        public void visitAddress(Address address) {
            // Now you can do something with the object using its proper
            // type...
        }         
    });


If you have lots of subclasses, you can always have an Adapter (a non-final class that implements the interface with empty implementations) to make creating the visitor class a little easier by extending the Adapter instead of implementing the Interface.

I hope this helps. I'm currently also having a problem with CGLIB proxying that I'm pretty sure is a bug (see my other post) that involves a special case where a subclass method returns 'this' but there are no useful responses as yet. Please anyone who can help have a look at that post as well (search for CGLIB AND ClassCastException).


Top
 Profile  
 
 Post subject:
PostPosted: Sun Sep 24, 2006 5:09 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
You will be able to cast, so long as you have called Hiberante.initialize() before the cast, like in my example. This deproxies the object, so it's not a CGLib object any more, it's real object of your class.

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


Top
 Profile  
 
 Post subject:
PostPosted: Sun Sep 24, 2006 7:28 pm 
Newbie

Joined: Mon Sep 18, 2006 10:45 am
Posts: 7
OK but you may want to keep your object proxied. Generally using the visitor pattern is a nice clean design feature but go for whatever is more appropriate for your own situation. I hope my contribution was useful anyway.
Now that I have your attention, perhaps you could take a look at my other post on this subject,CGLIB and ClassCastException. A ClassCastException is being thrown in the special case where a method returns 'this' and it is a subtype in a hierarchy as in the example in this topic.
I've had no useful responses as yet.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 31, 2007 10:51 am 
Newbie

Joined: Wed Jan 31, 2007 10:24 am
Posts: 4
tenwit wrote:
You will be able to cast, so long as you have called Hiberante.initialize() before the cast, like in my example. This deproxies the object, so it's not a CGLib object any more, it's real object of your class.


I've got the same problem, this seems to be a nice solution.
But it doesn't work in my case.

This is my abstract superclass:
Code:
@Entity
@Table(name = "DEVICE")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Device {...


And now I've two inherited classes, the one is:
Code:
@Entity
@DiscriminatorValue("SLOT_MACHINE")
public class SlotMachine extends Device {...


There's a third class, having the first as Member:
Code:
@Entity
@Table(name = "DEVICE_PLACE_PERIOD")
public class DevicePlacePeriod {
...
    private Device  device;
...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name  = "DEVICE_ID",
                        nullable = false)
    public Device getDevice() {
        return this.device;
    }


Now, when I try
Code:

DevicePlacePeriod dpp = ...
SlotMachine sm = (SlotMachine) dpp.getDevice();

I get the wellknown ClassCastExcpetion, even if I use Hibernate.initialize(dpp.getDevice()). But when I use Hibernate.getClass(dpp.getDevice()), the result is SlotMachine, not Device!

In Debug-Mode I've seen that the member 'device' in DevicePlacePeriod stays to be a Proxy after Hibernate.initialize(dpp.getDevice()). Just the target of the proxy moved from 'null' to a real SlotMachine.

What's wrong here? Exactly as you said, when I set fetch = FetchType.EAGER, everything is allright. But that's not what I want.

Big thanks for an answer.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 31, 2007 7:26 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
Nothing's wrong, that's how it works. You have to either deproxy the SlotMachine, or else provide a type member. The easier way to do that would be to add a read-only (update="false") member to Device, being the contents of the DTYPE column. By using that member instead of instanceof/explicit cast, you auto-deproxy when you need to.
Code:
Device dev = dpp.getDevice();
if ("SLOT_MACHINE".equals(dev.getType()) // This line deproxies, where dev instanceof SlotMachine wouldn't
{
  // This works because dev has already been deproxied
  SlotMachine slot = (SlotMachine) dev;
  ...
}

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


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 01, 2007 4:44 am 
Newbie

Joined: Wed Jan 31, 2007 10:24 am
Posts: 4
Big Thanks for fast reply!

I've tried excatly what you said, but it still doesn't work. Ineed, "SLOT_MACHINE".equals(dev.getType()) gives true when dev is a SlotMachine. Even Hibernate.getClass(dev) would return SlotMachine, not Device. But it doesn't really deproxy the device and I get the wellknown ClassCastException!

Then I made getType() in Device an abstract method, being implemented in SlotMachine. One time just returning "SLOT_MACHINE", the other time returning the contents of the DTYPE column. But it doesn't make a difference.

Perhaps there's something wrong with my Hibernate Configuration? I don't know. So I'm very thankful for any ideas, but if there are no, I think I can live with eager fetching in this case.

Big Thanks again!


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 01, 2007 4:33 pm 
Expert
Expert

Joined: Thu Dec 23, 2004 9:08 pm
Posts: 2008
The only other thing I can think of is something that I do purely for explicitness in my mapping files: I don't think that it's necessary. But give it a try. Set the @DiscriminatorValue on Device (the abstract superclass) to something that never appears in the DTYPE column: "" is a good bet. I'm told that this does nothing, but I always do it, and I don't get the problem that you're describing.

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


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 02, 2007 6:26 am 
Newbie

Joined: Wed Jan 31, 2007 10:24 am
Posts: 4
I'm sorry, it doesn't work. Perhaps there's something wrong with Annotations, I never tried this example with xml-mapping files. However, thanks for all the good ideas!


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 15, 2007 4:50 pm 
Beginner
Beginner

Joined: Tue Jun 28, 2005 2:43 pm
Posts: 29
Location: Silicon Valley
I too have run into this problem, given a model with quite a few polymorphic associations. As twrecke points out, tenwit's first solution doesn't quite get you there--Hibernate.initialize() does make sure that the proxied object is retrieved, but it can't replace current references to the proxy with the actual object. In other words, if B extends A, and the cglib-generated proxy also extends A,

Code:
A a = x.getA(); // proxy!
Hibernate.initialize(a);

if (a instanceof B) {
    // we will never get here, since a is an instance of the proxy class,
    // which is a direct subclass of A, and therefore a sibling of B
}


What we need is a way to dereference the proxy on demand. In my persistence layer, I have a general-purpose method:

Code:
public static <T> T deproxy(Object maybeProxy, Class<T> baseClass) throws ClassCastException {
   if (maybeProxy instanceof HibernateProxy)
      return baseClass.cast(((HibernateProxy) maybeProxy).getHibernateLazyInitializer().getImplementation());
   else
      return baseClass.cast(maybeProxy);
}


then we can say

Code:
A a = x.getA(); // proxy!
a = MyPersistenceLayer.deproxy(a, A.class);

if (a instanceof B) {
    // now we can get here, since we have converted the a proxy to its underlying pojo
    B b = (B) a;
    // do something with b
}


... or even

Code:
A a = x.getA(); // proxy!
try {
    B b = MyPersistenceLayer.deproxy(a, B.class);
    // do something with b
}
catch (ClassCastException e) {
    // can't use it
}


You may still see this as polluting the code, because the business layer does have to be aware of the persistence layer, but this is the price of using proxies for lazy loading. You can get some measure of insulation from Hibernate itself by making the deproxy method part of a persistence layer interface, implemented as shown only for the Hibernate-specific implementation of that interface.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 16, 2007 4:05 am 
Newbie

Joined: Wed Jan 31, 2007 10:24 am
Posts: 4
Thanks for reply! That's a very nice solution to my problem and indeed, it works fine!!!

Great thanks again!


Top
 Profile  
 
 Post subject:
PostPosted: Sun Mar 18, 2007 6:47 am 
Newbie

Joined: Sun Mar 18, 2007 6:32 am
Posts: 1
Thank you very much for the "MyPersistenceLayer" solution.

I now use this in combination with dozer to map a delta class with some changes to the retrieved class. So that I can update only a few attributes.

See the following code example (A user has a job and a salary..)

Job.java
Code:
package com.test;

public class Job {
   private Integer id;
   private String name;
   
// setters and getters are present but not written here in example
}


Salary.java
Code:
package com.test;

public class Salary {
   private Integer id;
   private Integer money;

// setters and getters are present but not written here in example   
}


and the User.java
Code:
package com.test;

public class User {
   private Integer id;
   private String name;
   private Job job;
   private Salary salary;

// setters and getters are present but not written here in example
}


The mapping from hibernate looks like:
Code:
<?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="com.test.User" table="users">
        <id name="id" column="id">
            <generator class="increment"/>
        </id>
        <property name="name" column="name"/>
       
        <many-to-one name="job" class="com.test.Job" column="job_id"/>
        <many-to-one name="salary" class="com.test.Salary" column="sal_id"/>
    </class>
   
    <class name="com.test.Job" table="jobs">
        <id name="id" column="id">
            <generator class="increment"/>
        </id>
        <property name="name" column="name"/>
    </class>
   
    <class name="com.test.Salary" table="salaries">
        <id name="id" column="id">
            <generator class="increment"/>
        </id>
        <property name="money" column="money"/>
    </class>

</hibernate-mapping>


And the dozer mapping looks as follows:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mappings PUBLIC "-//DOZER//DTD MAPPINGS//EN"
   "http://dozer.sourceforge.net/dtd/dozerbeanmapping.dtd">
<mappings>

  <mapping map-null="false">
    <class-a>com.test.Job</class-a>
    <class-b>com.test.Job</class-b>   
  </mapping>   
 
  <mapping map-null="false">
    <class-a>com.test.Salary</class-a>
    <class-b>com.test.Salary</class-b>   
  </mapping>
 
  <mapping map-null="false">
    <class-a>com.test.User</class-a>
    <class-b>com.test.User</class-b>
   
    <field copy-by-reference="true">
        <a>job</a>
        <b>job</b>
   </field>
   
   <field copy-by-reference="true">
        <a>salary</a>
        <b>salary</b>
   </field>
   
  </mapping>
</mappings>



In my main code I can now do the following:
Code:
private void addUser() {
       Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
       
        // Set a new Job to the user, first create a delta-user
        // this deltaUser has a new name, new job and new salary,
        // but we are talking about the same user
        final User deltaUser = new User();
        deltaUser.setName("'delta name'");
       
        final Job searchedJob3 = (Job) session.load(Job.class, Integer.valueOf(3));
        deltaUser.setJob(searchedJob3);
        final Salary searchedSal3 = (Salary) session.load(Salary.class, Integer.valueOf(3));
        deltaUser.setSalary(searchedSal3);
       
        // lookup the real-user
        final User user = (User) session.load(User.class, Integer.valueOf(1));
       
        // deproxy the user, else dozer won't work correct
        final User deproxyUser = MyPersistenceLayer.deproxy(user, User.class);
       
        // use dozer to map the delta user to deproxy user (do not map null values, see dozer configuration.)
        mapper.map(deltaUser, deproxyUser);
       
        session.flush();
        session.getTransaction().commit();
    }



The combination from Hibernate + dozer allows me to workaround the "do not update null-values in database" issue.
This works very good if you have multiple attributes in the classes, in this way no programming is needed but everything can be done using configuration.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 18 posts ]  Go to page 1, 2  Next

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.