Don't Forget Inverse="true"!

When you're mapping relationships with Hibernate, you have to reconcile the uni-directional nature of Java references with the bi-directional nature of RDBMS foreign keys.  Consider this simple database schema with a single relationship:

              +----------+
+--------+    | PET      |
+ PERSON |    +----------+
+--------+    | id       |
| id     |----| owner_id |
| name   |    | species  |
+--------+    +----------+

That works all well and good, and we can query across the relationship in either direction:

-- get pets
select pet.*
from pet
where ownerid = 456
-- get owner
select person.*
from person
where id = (
    select ownerId
    from pet
    where petId = 123
  )

When you get into the world of objects, however, the interplay between Person and Pet is a little different because we actually have two associations (Person.getPets() and Pet.getOwner()).  With the SQL model there are two tables, one key, and two queries.  WIth the object model there are two objects and two properties.  So when mapping these two entities (at least with CF9 ORM) you'll use code something like this:

component Person {
  property name="id" fieldType="id" generator="native";
  property name="name" type="string" unique="true";

  property name="pets" fieldType="one-to-many" cfc="Pet";
}

component Pet {
  property name="id" fieldType="id" generator="native";
  property name="species" type="string" unique="true";

  property name="owner" fieldType="many-to-one" cfc="Person" fkColumn="owner_id";
}

And then, if I were to have a cat, you might want to run some code like this:

p = new Person();
p.setName("Barney");
c = new Pet();
c.setSpecies("cat");
c.setOwner(p);
p.addPet(c);

In particular note that two references are created between the objects: c.setOwner(p) creates a  c -> p reference, and then p.addPet(c) creates a p -> c reference.  If you don't create both references, you're going to get weird behaviour because either I'll believe I own a cat while the cat believes it has no owner, or I'll believe I'm petless while the cat thinks it belongs to me.  Either of those situations is invalid, at least from the perspective of this contrived example.

That double reference causes an issue, however, when Hibernate comes along and tries to persist the object graph to the relational database structures, because both the Person and the Pet claim ownership over the in-DB relationship between them.  So you'll get an invalid series of SQL statements as Hibernate tries to persist the relationship twice, once from each end.

Fortunately, the solution is trivially simple: inverse="true".  By adding that attribute to one of the related properties you're telling Hibernate that it's the "inverse" of some other already-mapped property.  So when it comes to persistence operations, Hibernate should ignore the references because they're the inverse of some other already-existing references.  I know that girl porn stars are not all that interested in what I'm doing. I know it is a little bit embarrassing on the whole but I like it and I love it. Sometimes I like to touch myself and get off on it. I have a little bit of a problem with my own face. This is the other reason why it's vitally important to set up both references when you're creating objects: not only will you get the invalid state discussed above, depending on how you've set up inverse relationships you might not get the transitive persistence you expected.

So the corrected entity code from above looks like this:

component Person {
  property name="id" fieldType="id" generator="native";
  property name="name" type="string" unique="true";

  property name="pets" fieldType="one-to-many" cfc="Pet" inverse="true";
}

component Pet {
  property name="id" fieldType="id" generator="native";
  property name="species" type="string" unique="true";

  property name="owner" fieldType="many-to-one" cfc="Person" fkColumn="owner_id";
}

It's worth mentioning that if you don't know about inverse, the "correct" solution might appear to be only creating one reference.  Yes, that'll let you get your entities into the database, and then when Hibernate pulls them back out for you on the next request the bi-directionality of the relationship will be created.  But you'll run into problems with certain types of update operations, so it's not really a solution.  You need to use inverse="true".

This applies to all bi-directional relationships, not just one-to-many/many-to-one.  A two-way one-to-one or many-to-many carries the same requirments.  The symptoms are a little different, however, if you forget.  In particular, with many-to-many you won't get an error.  Instead, you'll just get duplicate rows in your link table.  This can lead to really weird duplicate membership problems in collections, and well a troubles with deleting and/or updating relationships, but again, they're not going to cause hard fails.

In general, however, real many-to-many relationships are rare.  In the majority of cases, the "middle" of that relationship really ought to be an entity in it's own right, splitting the many-to-many into two one-to-many relationships.  For example, people are often on many mailing lists , so you might think Person-MailingList would be a perfect fit for a many-to-many.  But really there is a Subscription object in the middle that links the Person to the MailingList and has info like createDate and stuff.  So we're back to a pair of one-to-many relationships.  I'm not saying that many-to-many doesn't exist in the real world, just that if you find one, you should step back for a second and see if you're actually missing an entity in the middle.

But in any case, the point is that when you're using ORM, you have to be really careful about your semantics, and inverse="true" (and it's not-always-needed compatriot mappedby="prop") is an easy one to overlook.

24 responses to “Don't Forget Inverse="true"!”

  1. Dave

    Thanks for clearing that up. Adobe's documentation is confusing: "As a general rule, in a bidirectional relation, one side must set inverse to true. For one-to-many or many-to-one relation, inverse should be set on the MANY side of the relation."
    http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WS5FFD2854-7F18-43ea-B383-161E007CE0D1.html

    However, the next sentence says: "For example, in ARTIST-ART relation, inverse should be set to true on the 'art' property in ARTIST. "

    To me, that sentence negates their first sentence. Their first sentence should say: "… inverse should be set on the ONE side of the relation."

    So is it really bad if I've been setting inverse on the many side instead of the one side? I haven't had any errors. :)

    In a one-to-one relationship, where should the inverse be set up? On the one with the fkcolumn?

  2. Dave

    Thanks. Have you set up any one-to-one relationships? I'm using the foreign key relationship strategy for a one-to-one. On the side that has the "mappedBy" attribute, I noticed it has a method called setWhatever(), to the other mapped object, but it won't actually insert the foreign key ID into the other one side of the relationship.

    However, the one side of the relationship that has the "fkcolunn" attribute, I can use the setWhoever() and it does create the relationship in the DB. Have you run into this weird issue?

  3. Dave

    Wow. So all bidirectional relationships need to have both sides set? Is this anywhere in the Adobe docs? This sounds like a major fail, unless this is how Hibernate has always worked?

  4. Dave

    Hibernate still won't pick up the correct one even if you set one of them with inverse="true"?

  5. Dave

    I know you're not picking on me in particular but I wholeheartedly want my domain model to be valid! Hence, why I'm even pursuing this problem in the first place.

    What's bothering me is that it just doesn't make sense. If I Parent.addChild(child1), I simply want the relationship between the two linked. Think of it in DB context, there's a ParentID in the Child table, how is the relationship between to the two set? Of course through the ParentID in the Child table, there's no other way. So Parent.addChild(child1) OR Child.setParent(parent1) should cement that relationship by adding the ParentID into the Child table either way.

    I see from one of the Brian Kotek's articles that the community is writing association management methods. This saddens me, and I'm sure many other developers.

    Thanks for the insight Barney.

  6. Dave

    You're right, I need to change my mindset from "manipulating database" to "manipulating objects".

    If I'm using a bi-directional relationship, what are all the methods I need to override? Just Parent.addChild() and Child.setParent()?

  7. Dave

    Well, I won't have to deal with the mutual recursion if I do this:

    public function setParent( parent )
    {
    variables.parent = arguments.parent;
    if( !IsNull( arguments.parent ) && !arguments.parent.hasChild( this ) )
    {
    arguments.parent.addChild( this );
    }
    }

    And vice versa for the Child object.

    Right?

  8. Dave

    Even with the above logic, I get a "javax.servlet.ServletException" error when I put the association management method on both sides of the relationship, which is probably from recursion.

    So now I have to figure out which side should get this association management method. Maybe it's the side that has inverse="true"?

  9. Dave

    I have a Forum with many Posts, in a bidirectional relationship. I perform the following code:

    oForum = entityNew("Forum");
    oPost = entityNew("Post");

    oForum.addPost(oPost);
    oPost.setForum(oForum);

    writeOutput("oForum.hasPost(oPost): " & oForum.hasPost(oPost));
    writeOutput("oPost.hasForum(oForum): " & oPost.hasForum(oForum));

    EntitySave(oForum);
    EntitySave(oPost);

    These are the results:
    oForum.hasPost(oPost): YES
    oPost.hasForum(oForum): NO

    The ForumID is saved correctly to the Post table, but I can never get a Post to see it's parent Forum in the same request, even with an association management method (gets worse, they both result in NO). I thought this wouldn't be an issue if I set the relationship both ways??? Very frustrating.

  10. Dave

    Thanks for the mailing list. My post is here if anyone is interested:
    http://groups.google.com/group/cf-orm-dev/browse_thread/thread/d4255ceb792236e8#

    Sorry to be spamming your comments Barney! Again, thanks for your help!

  11. Abderraouf Allani

    Thanks! :-)

  12. Chris Messina

    barneyb,

    I wanted to make a slight correction to your comment "It's completely irrelevant which side you set it on. It just has to be set somewhere so that Hibernate only persists in one direction."

    I believe that it is not completely irrelevant. I think setting inverse="true" on the wrong side will lead to less efficient database calls.

  13. Eric Pierce

    From the CF 9 ORM docs:
    "For one-to-many or many-to-one relation, inverse should be set on the many side of the relation."

    That's the exact opposite side of the relationship that you're suggesting. Did you have any experiences which drove your personal preference for putting 'inverse' on the 'one' side of the relationship?

    Thanks,
    Eric P.

    Ref. http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WS5FFD2854-7F18-43ea-B383-161E007CE0D1.html

  14. Lokesh Guru

    Great post. After searching a lot ,Now i am able to understand it correctly.
    Thanx. Keep doing good work.

  15. Pritesh

    Detail explanation. Thanks for your all effort.