-->
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.  [ 1 post ] 
Author Message
 Post subject: Cascading update with business key equality: best practices?
PostPosted: Wed Jun 17, 2009 1:03 pm 
Newbie

Joined: Tue Jun 16, 2009 1:47 pm
Posts: 1
Hello,

I'm new to Hibernate, and while there are literally tons of examples to look at, there seems to be so much flexibility here that it's sometimes very hard to narrow all the options down the best way of doing things. I've been working on a project for a little while now, and despite reading through a lot of books, articles, and forums, I'm still left with a bit of a head scratcher. Any veteran advice would be very appreciated.

So, I have a model involving two classes with a one-to-many relationship from parent to child. Each class has a surrogate primary key and a uniquely constrained composite business key.

Code:

    
<class name="Container">
        <id name="id" type="java.lang.Long">
            <generator class="identity"/>
        </id>
        <properties name="containerBusinessKey" unique="true" update="false">
            <property name="name" not-null="true"/>
            <property name="owner" not-null="true"/>
        </properties>
        <set name="items" inverse="true" cascade="all-delete-orphan">
            <key column="container" not-null="true"/>
            <one-to-many class="Item"/>
        </set>
    </class>

    <class name="Item">
        <id name="id" type="java.lang.Long">
            <generator class="identity"/>
        </id>
        <properties name="itemBusinessKey" unique="true" update="false">
            <property name="type" not-null="true"/>
            <property name="color" not-null="true"/>
        </properties>
        <many-to-one name="container" not-null="true" update="false" class="Container"/>
    </class>
  

The beans behind these mappings are as boring as you can possibly imagine--nothing fancy going on. With that in mind, consider the following code:

Code:

    Container c 
= new Container("Things", "Me");
    c.addItem(new Item("String", "Blue"));
    c.addItem(new Item("Wax", "Red"));

    Transaction t = session.beginTransaction();
    session.saveOrUpdate(c);
    t.commit();
  

Everything works fine the first time, and both the Container and its Items are persisted. If the above code block is executed again, however, Hibernate throws a ConstraintViolationException--duplicate values for the "name" and "owner" columns. Because the new Container instance has a null identifier, Hibernate assumes it is an unsaved transient instance. This is expected but not desired. Since the persistent and transient Container objects have the same business key values, what we really want is to issue an update.

It is easy enough to convince Hibernate that our new Container instance is the same as our old one. With a quick query we can get the identifier of the Container we'd like to update, and set our transient object's identifier to match.

Code:

    Container c 
= new Container("Things", "Me");
    c.addItem(new Item("String", "Blue"));
    c.addItem(new Item("Wax", "Red"));

    Query query = session.createSQLQuery("SELECT id FROM Container WHERE name = ? AND owner = ?");
    query.setString(0, c.getName());
    query.setString(1, c.getOwner());
    BigInteger id = (BigInteger)query.uniqueResult();
    if (id != null) {
        c.setId(id.longValue());
    }

    Transaction t = session.beginTransaction();
    session.saveOrUpdate(c);
    t.commit();
  

This almost satisfies Hibernate, but because the one-to-many relationship from Container to Item cascades, the same ConstraintViolationException is also thrown for the child Item objects.

My question is: what is the best practice in this situation? It is highly recommended to use surrogate primary keys, and it is also recommended to use business key equality. When you put these two recommendations in to practice together, however, two of the greatest conveniences of Hibernate--saveOrUpdate and cascading operations--seem to be rendered almost completely useless. As I see it, I have only two options:

  • Manually fetch and set the identifier for each object in the mapping. This clearly works, but for even a moderately sized schema this is a lot of extra work which it seems Hibernate could easily be doing.
  • Write a custom interceptor to fetch and set object identifiers on each operation. This looks cleaner than the first option but is rather heavy-handed, and it seems wrong to me that you should be expected to write a plug-in which overrides Hibernate's default behavior for a mapping which follows the recommended design.

Is there a better way? Am I making completely the wrong assumptions? I'm hoping that I'm just missing something.

Thanks.


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

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.