April 25, 2006

Hibernates bizarre interpretation of inverse ;)

Writing about web page hibernate.org

So my colleague asked me about the exact semantics of hibernate's "inverse" keyword which is surely the worst choice of word for a simple concept.

Essentially "inverse" indicates which end of a relationship should be ignored, so when persisting a parent who has a collection of children, should you ask the parent for its list of children, or ask the children who the parents are?

Why does this matter? Well if you are stupid then you might do something like:


  Parent parentA = new Parent();
  Parent parentB = new Parent();
  Child child = new Child();
  parentA.getChildren().add(child);
  child.setParent(parentB);

how should hibernate persist this situation? For unidirectional one–to–many it is trivial; only one end of the relationship is modelled (there is only parent.addChild(), not child.getParent()), but when it is bidirectional (parent.getChild and child.getChildren) you need to indicate whether the one–to–many is inverse or not.

What does it mean to be inverse? It informs hibernate to ignore that end of the relationship. If the one–to–many was marked as inverse, hibernate would create a child–>parentB relationship (child.getParent). If the one–to–many was marked as non–inverse then a child–>parentA relationship would be created.

Or at least that is what you would think :) First moan is why use the word "inverse"? By setting it to true you are effectively telling hibernate to ignore it. Setting something to true to exclude something is reverse logic and is incredibly unintuitive. Maybe "owner" would be a better term?

The second moan is that hibernate isn't consistent. If the parent–>child is not inverse (i.e. parent.getChildren will be used to define the relationship) then you would expect calling child.setParent to have no effect. This is not the case. Calling child.setParent does create the relationship. If you end up with the idiotic case I proposed earlier, than hibernate will give precendece to the parent.getChildren (as it should), but to be consistent child.setParent(parentA) should not create the relationship.

It is such a simple concept, but so many people seem to struggle with it. I really don't think hibernates terminology is very helpful.

Simply put; if you set inverse=true on a one–to–many then child.getParent will be called, if inverse=false then parent.getChildren will be called.

Of course, good developers will not run into this problem because they will all ensure that parents have appropriate accessors (parent.addChild, parent.removeChild etc.) :)

The invertedness (?) of the relationship does have quite an impact on performance and schema design; if the one–to–many is not inverse (default) then everytime you create a child, hibernate will execute 2 statements, one to create the child, one to update the child with the foreign key of the parent. Foreign key constraints/not null constraints? hah, who needs those anyway :) Hibernate will also instantiate the collection everytime you add a child….not sure why. Setting inverse to true will fix both of these annoyances/downright ridiculous design decisions.

One last note; cascade and inverse are completely orthogonal. Cascade simply tells hibernate how to reach objects. If I set inverse=true on a one–to–many then hibernate will not use parent.getChildren to define the relationship. If I set cascade to save, or all then hibernate will call parent.getChildren to see if there are any new children. Setting inverse=true and calling child.setParent will have absolutely no effect because hibernate won't know about your new child :)

So summary:

– always provide parent.addChild which updates both ends of the relationship

– always set inverse to true on bidirectional one–to–many

– make sure you understand the difference between cascades and inverse ;)

P.S. Version 3.1.2 was used.

Code used for inverse relationship


<?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="uk.ac.warwick.Child" table="child">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    <many–to–one name="parent"
                 class="uk.ac.warwick.Parent"
                 column="parent_id"/>
  </class>
</hibernate–mapping>

<?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="uk.ac.warwick.Parent" table="parent">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    
    <bag name="children" inverse="true" cascade="save–update">
      <key column="parent_id"/>
      <one–to–many class="uk.ac.warwick.Child"/>
    </bag>
  </class>
</hibernate–mapping>

package uk.ac.warwick.inverse;

import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;

public final class TestRelationships extends TestCase {
    public void testChildrenOfParentAreIgnored() {
        Configuration config = new Configuration();
        config.addResource("uk/ac/warwick/inverse/Child.hbm.xml");
        config.addResource("uk/ac/warwick/inverse/Parent.hbm.xml");
        
        config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
        config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
        config.setProperty("hibernate.show_sql", "true");
        config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
        config.setProperty("hibernate.connection.username", "sa");
        
        SessionFactory factory = config.buildSessionFactory();
        Session session = factory.openSession();
        
        Parent parentA = new Parent();
        session.save(parentA);
        session.flush();
        
        /**
         * Because parent–>child is inverse, adding the child to the parent
         * will not associate it with the parent.
         * 
         * Cascade will save it though.
         */
        Child childA = new Child();
        parentA.getChildren().add(childA);
        session.save(parentA);
        session.flush();

        session.evict(childA);
        assertEquals("cascade works so child should be saved",
                     1,
                     session.createCriteria(Child.class).list().size());

        childA = (Child) session.load(Child.class, childA.getId());
        assertNull("childAs parent should not be set!",
                   childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertTrue("but it shouldnt update the children of parentA",
                   parentA.getChildren().isEmpty());
        
        childA.setParent(parentA);
        session.save(childA);
        session.flush();
        
        /**
         * Because child–>parent is not inverse, setting the parent
         * will create the association 
         */
        childA.setParent(parentA);
        session.save(childA);
        session.flush();

        session.evict(childA);
        childA = (Child) session.load(Child.class, childA.getId());
        assertEquals("childAs parent should now be set!",
                   parentA,
                   childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertFalse("and parentA should have children",
                   parentA.getChildren().isEmpty());
     }
}

Code used for non inverse relationship


<?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="uk.ac.warwick.Child" table="child">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    <many–to–one name="parent"
                 class="uk.ac.warwick.Parent"
                 column="parent_id"/>
  </class>
</hibernate–mapping>

<?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="uk.ac.warwick.Parent" table="parent">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    
    <bag name="children" inverse="false" cascade="save–update">
      <key column="parent_id"/>
      <one–to–many class="uk.ac.warwick.Child"/>
    </bag>
  </class>
</hibernate–mapping>

package uk.ac.warwick.non_inverse;

import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;

public final class TestRelationships extends TestCase {
    public void testParentOfChildrenAreIgnored() {
        Configuration config = new Configuration();
        config.addResource("uk/ac/warwick/non_inverse/Child.hbm.xml");
        config.addResource("uk/ac/warwick/non_inverse/Parent.hbm.xml");
        
        config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
        config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
        config.setProperty("hibernate.show_sql", "true");
        config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
        config.setProperty("hibernate.connection.username", "sa");
        
        SessionFactory factory = config.buildSessionFactory();
        Session session = factory.openSession();
        
        Parent parentA = new Parent();
        session.save(parentA);
        session.flush();

        /**
         * Even though child–>parent is inverse, setting the parent
         * will associate it with the parent.
         */
        Child childA = new Child();
        childA.setParent(parentA);        
        session.save(childA);
        session.flush();

        session.evict(childA);
        assertEquals("cascade works so child should be saved",
                     1,
                     session.createCriteria(Child.class).list().size());

        childA = (Child) session.load(Child.class, childA.getId());
        assertNotNull("childAs parent will be set!",
                     childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertFalse("and it will update the children of parentA",
                   parentA.getChildren().isEmpty());
     }
}

- 18 comments by 2 or more people Not publicly viewable

[Skip to the latest comment]
  1. Hongfeng Sun

    Thanks for very detailed example. Very helpful !

    26 Apr 2006, 16:08

  2. Brad

    Excellent post and thanks for the clarification! I was specifically trying to understand the inconsistencies with how inverse="true" works, and I'm glad to see that I'm not the only one who's seen it.

    One question though. It seems like this line:

    Calling parent.setChild does create the relationship.

    should instead be:

    Calling child.setParent does create the relationship.

    right?

    29 Jun 2006, 16:14

  3. Nice catch. Thanks :)

    29 Jun 2006, 16:31

  4. Brad

    no problem, and thanks for the quick response!
    :)

    29 Jun 2006, 17:30

  5. Brett

    I put a not-null=”true” on the element and was able to keep the foreign key and not nullable constraints on the child table. The insert statment looks correct. However, there is still the second SQL statement, the UPDATE. Any ideas why the update is needed?

    13 Sep 2006, 20:10

  6. Katalma

    My experience is if you use inverse=”true” on one-to-many (parent) side in bidirectional relation, you should not use insert=”true” and update=”true” on many-to-one (child) side. Because the effect is the same: first an insert without the parent id, which causes problems if you have not-null and foreign-key constraints… But I do not really understand the correspodence.

    18 Dec 2006, 16:35

  7. Katalma

    Of course, I mean insert=”false” and update=”false” :)

    19 Dec 2006, 10:15

  8. Marcelo

    Very helpful post.

    09 Jan 2007, 13:48

  9. Scott

    Colin—

    I have been on a cruise in the Caribbean and reading about Hibernate for five days! The one thing that never made sense was “inverse.” If I would have had internet access to this article, I could have spent much more time at the pool bar. This is a great explanation my brother. Where are you located? I feel like the light just came on and I should repay you in some way. Dinner if you are close? I’m in St. Louis.

    Peace,
    Scott

    13 Jan 2007, 13:33

  10. zhang

    I think, attribute inverse is only valide for the managed POs: I have the objects hierarchy, which set up using the one-directional reference outside hibernate (one-to-manay inverse=”true”), then using hibernate to persistent it in the database. But I can not get what I want, only the root element is saved and other elements in the hierarchy not.

    19 Mar 2007, 14:49

  11. Prabakaran K

    hi all,
    i am new to hibernate i want know below mapping is valid?.If its valid while inserting Contact (or) FeedType

    it need to be inserted in FEED_TYPE_DEPENDENT_CONTACT while inserting DEPENDENT_CONTACT_SEQUENCE sequence is not getting generated.i getting the exception like this

    cannot insert NULL into (“CST_DEV”.”FEED_TYPE_DEPENDENT_CONTACT”.”DEPENDENT_CONTACT_ID”)

    formed query is insert into FEED_TYPE_DEPENDENT_CONTACT (CONTACT_ID, FEED_TYPE_ID) values (?, ?)

    plz any one help out?

    DEPENDENT_CONTACT_SEQUENCE

    09 Apr 2007, 10:24

  12. Keerthi

    Thank god I found this post. I simply don’t understand why the hibernate designers have to name it so obscurely. As colin mentions, “owner” would have been better and straightforward. Great post.

    23 May 2007, 22:40

  13. Prasanth

    Very useful blog…..Thanks a lot Mr.Colin…

    05 Jul 2007, 13:22

  14. ravi

    good

    31 Jul 2007, 16:20

  15. Tamil

    Really helpful , Thanks

    20 Aug 2007, 16:25

  16. sandeep

    Indeed a very good explaination.

    24 Aug 2007, 14:12

  17. Sandeep

    Good Explaination but some confusion is still there :

    1. If the one–to–many was marked as inverse, hibernate would create a child–>parentB relationship (child.getParent).

    Q- child.getParent() or child.setParent() ?
    Q- creating child–>parentB relationship means which end will be made persistent ?

    24 Aug 2007, 15:06

  18. Sandeep

    can you please let me know what do u mean by
    “hibernate would create a child–>parentB relationship (child.getParent)” ?

    27 Aug 2007, 07:44


Add a comment

You are not allowed to comment on this entry as it has restricted commenting permissions.

Trackbacks

April 2006

Mo Tu We Th Fr Sa Su
Mar |  Today  | May
               1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Search this blog

Tags

Galleries

Most recent comments

  • Interesting… While I'm not completely convinced in such microbenchmarks, I'm pretty sure that 1ms = … by Alexander Snaps on this entry
  • Hello. I bought the book yesterday. I was trying to find the source code for chapter 11 and chapter … by Suleman on this entry
  • http://woosight.net/account/login?username=demo by live mashup demo on this entry
  • Thanks mate .. This blog was really helpful. by Maaz Hurzuk on this entry
  • Ty. Not directly helpful for my problem, but pointed me in the right direction. You will also get th… by Mike E. on this entry

Blog archive

Loading…
Not signed in
Sign in

Powered by BlogBuilder
© MMXIV