-->
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: SQL manual data migration, problems with polymorphic entity
PostPosted: Wed Nov 09, 2011 11:34 pm 
Beginner
Beginner

Joined: Wed Sep 30, 2009 5:29 am
Posts: 29
Hi all,

Hibernate 3.6.5.Final, MySQL 5.5, JDK 1.7.

I've extended an Entity to have a new child class using the JOINED inheritance strategy, and tried to migrate data in using low level SQL (a Liquibase changelog) rather than Java code to create the database rows for the Entities. I find that fetching these migrated entities fails to recognise them as specialised types. For example, ChildEntity is derived from ItemEntity, and when I fetch an ItemEntity with:
Code:
ItemEntity i = (ItemEntity)session.load(ItemEntity.class, "the_id");

A ChildEntity created via Java and persisted by Hibernate is fetched as a ChildEntity instance, and I can see Hibernate generate SQL with a JOIN between the two tables, as expected. However, a ChildEntity manually inserted by me is fetched as a plain ItemEntity. My question is about how to manually insert data so that Hibernate can fetch it as a ChildEntity instance.

Some Background:
-----------------------

I have a data model I've used for a few years with a simple @MappedSuperclass and single concrete Entity class, backed by a single table. Very simple. Here's some example pseudocode (not necessarily precisely correct).
Code:
@MappedSuperclass
public class AbstractPersistentEntity implements Serializable {
    @Id
    int id;
}

@Entity
@Table(name="items")
public class ItemEntity extends AbstractPersistentEntity {
    @Column(name="item_attr")
    String itemAttribute;
}

Based on that, I have a single table in the database called "items":
Code:
TABLE items ( id INTEGER PRIMARY KEY, item_attr VARCHAR(255) );

The New ChildEntity Class
--------------------------------

Now I've introduced ChildEntity. I find I have to specify an InheritanceStrategory for ItemEntity, where I didn't before:
Code:
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="items")
.. as before

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="items_child")
public class ChildEntity extends ItemEntity {
    @Column
    String child_attr;
}

If I let hbm2ddl create the database schema I get:
Code:
TABLE items ( id INTEGER PRIMARY KEY NOT NULL, item_attr VARCHAR(255) );
TABLE items_child ( id INTEGER PRIMARY KEY NOT NULL, child_attr VARCHAR(255) );

Since the "items" table already existed, my migration task is to populate "items_child" with suitable attributes so the ChildEntity entities are complete.
Code:
    INSERT INTO items_child (id, child_attr)
    SELECT i.id, at.attributes
    FROM   items i
    INNER JOIN some_data_source at ON at.join_key = i.id
    WHERE at.something = 'value';

This works fine and I can see the "items_child" rows created, with id's matching those in "items", but I'm missing some magic sauce that Hibernate seems to need, because no session.load(ItemEntity.class, "the_id") calls will fetch them as ChildEntity instances.

If I use the single table inheritance strategy, hbm2ddl creates a DTYPE column with the entity type clearly expressed, but I cannot see how the same information is expressed when using the joined strategy. I have a small test project with just the two entities in a cleanroom database, and cannot see how Hibernate knows which are ItemEntity instances and which are ChildEntity instances.

Can anyone suggest what I'm missing, what FAQ I haven't read, or what pieces of magic eludes me? Or would posting some actual code here help?

If I cannot crack this, I'm going to resort to writing the migration code in Java and let Hibernate do its persistence magic, but that would defeat the purpose of using Liquibase as a database change tracking tool, which forces me to use low level SQL to do the job. That will be some time next week, or the week after.

Many Thanks
Nick


Top
 Profile  
 
 Post subject: Re: SQL manual data migration, problems with polymorphic entity
PostPosted: Thu Nov 10, 2011 6:16 am 
Beginner
Beginner

Joined: Wed Sep 30, 2009 5:29 am
Posts: 29
A little update.. I've found that a manual insert in my little test app does actually work. I insert into "items" and "items_child" with the same id for each, then do a session.load(ItemEntity.class, "the_id"), and it returns a ChildEntity instance.

I'm going to have to take some time going over my live application, which isn't going to happen till next week, to see if I can reproduce this success there. I'd still like to hear how Hibernate knows to instantiate the child entity instance, just so I know what I'm looking for as I go back over it.


Top
 Profile  
 
 Post subject: Re: SQL manual data migration, problems with polymorphic entity
PostPosted: Wed Dec 28, 2011 12:45 am 
Beginner
Beginner

Joined: Wed Sep 30, 2009 5:29 am
Posts: 29
neek wrote:
I'm going to have to take some time going over my live application, which isn't going to happen till next week, to see if I can reproduce this success there. I'd still like to hear how Hibernate knows to instantiate the child entity instance, just so I know what I'm looking for as I go back over it.

I'm starting to think I'm being tripped up by instanceof not behaving as I expect. Examining the log as my app (both trivial test and live app) does a session.load() for an entity of the base type shows that Hibernate does do a JOIN on the child type's table, as expected.
Code:
select testitemen0_.id as id0_0_, testitemen0_.basic_item_attribute as basic2_0_0_, testitemen0_1_.zencart_item_attribute as zencart1_1_0_, case when testitemen0_1_.id is not null then 1 when testitemen0_.id is not null then 0 end as clazz_0_ from testitems testitemen0_ left outer join testitems_zencart testitemen0_1_ on testitemen0_.id=testitemen0_1_.id where testitemen0_.id=?

There seems to be some magic in there about detecting whether the item is of the child type of not, see the clazz_0_ result element.

Now, calling toString on the returned entity does invoke the child class's toString() function, but the instanceof operator claims the object is not an instance of the child class, and casting the object to the child type throws a ClassCastException.

The code, which reads the entity as a TestItemEntity then tests to see if it's actually the child type, TestZenCartEntity:
Code:
   TestItemEntity manualti = (TestItemEntity)session.load(TestItemEntity.class, "1234");
         logger.info("Read manually inserted test zen cart item: " + manualti +
            " .. instance of test: " + (manualti instanceof TestZenCartEntity));
         TestZenCartEntity castmanualti = (TestZenCartEntity)manualti;
         logger.info("Cast object: " + castmanualti + " .. instanceof test: " + (castmanualti instanceof TestZenCartEntity));

Log output:
Code:
Dec 28, 2011 11:36:13 AM test.InheritanceTest.Main runTest
INFO: Read manually inserted test zen cart item: ZenCartEntity [id=1234, basic attribute=1234 zencart attribute=4321] .. instance of test: false
Dec 28, 2011 11:36:13 AM test.InheritanceTest.Main runTest
SEVERE: Fatal: test.InheritanceTest.TestItemEntity_$$_javassist_0 cannot be cast to test.InheritanceTest.TestZenCartEntity
java.lang.ClassCastException: test.InheritanceTest.TestItemEntity_$$_javassist_0 cannot be cast to test.InheritanceTest.TestZenCartEntity
        at test.InheritanceTest.Main.runTest(Main.java:67)
        at test.InheritanceTest.Main.main(Main.java:24)


This seems impossible to me. How can the TestZenCartEntity's toString() method be invoked, yet the instanceof and cast operations fail to recognise it as an instance of TestZenCartEntity?

I shall keep trying! Perhaps I need to learn about javassist and the way Hibernate proxies work.
Nick


Top
 Profile  
 
 Post subject: Re: SQL manual data migration, problems with polymorphic entity
PostPosted: Thu Dec 29, 2011 12:21 am 
Beginner
Beginner

Joined: Wed Sep 30, 2009 5:29 am
Posts: 29
neek wrote:
Perhaps I need to learn about javassist and the way Hibernate proxies work.

Looks like I was ignorant of the fact that a session.load() may (always?) return a javassist proxy to the entity object, so doing instanceof checks on it is unsafe. My manually inserted data does seem to be recognised by Hibernate and fetched as the correct entity classes, but the actual object returned is not an instance of any of my entity classes, rather it's a javassist proxy object. My testing with instanceof was completely confusing me.

The safe thing to do here, apparently, is to use session.get() instead, which will always return a completely initialised and concrete instance of your Entity type. So, instead of:
Code:
MyEntity e = session.load(MyEntity.class, "1234");
if (e instanceof MyChildEntity) /* UNSAFE */

One should do:
Code:
MyEntity e = session.get(MyEntity.class, "1234");
if (e instanceof MyChildEntity) /* safe */

However, in several instances I fetch entities from collections, e.g. a OneToMany of another entity may contain a collection of MyEntity instances, some of which might be MyChildEntities. In this case they might be proxy object and to do a further get() on their id to fetch concrete instances seems a bit crazy. Two approaches I've found here here:
1/ Use Hibernate.getClass() to return a Class object of the object's actual entity class. I found this link useful: http://stackoverflow.com/questions/1139 ... ate-entity specifically Daniel Bleisteiner's comment about Hibernate.getClass. I found that HibernateProxyHelper.getClassWithoutInitializingProxy returned MyEntity, whereas Hibernate.getClass() returned MyChildEntity as required.
Code:
MyEntity theEntity = someCollection.get(0); /* Get a (possibly proxied) object from a collection of MyEntity objects */
if (MyChildEntity.class.isAssignableFrom(Hibernate.getClass(theEntity)) /* safe */

2/ Use some helper code I found to fully instantiate an entity so that it's definitely not a proxy any more: http://stackoverflow.com/questions/2216 ... eal-object
MyEntity theEntity = someCollection.get(0); /* Get a (possibly proxied) object from a collection of MyEntity objects */
Code:
if (initializeAndUnproxy(theEntity) instanceof MyChildEntity) /* safe */


Looks like StackOverflow is a better resource than the Hibernate Forums, at least in this regard :)


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.