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.
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?
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 usually put it on the 'one' side, which means the normal (non-inverse) is the table with the foreign key. To put it another way, the side without the foreign key is inverse. But that's purely personal preference, and has no impact on any functionality (as long as you're managing your in-memory references correctly).
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?
Dave,
This isn't a "weird" issue, it's exactly what you've told Hibernate to do. When you create a relationship between two persistent objects, you MUST create both sides of the relationship. I.e. you need to call setWhatever AND setWhoever. That way your in-memory model is sound, and then Hibernate will ensure it's persisted correctly. The fact that it works "correctly" when you only call setWhoever is coincidence, not intended behaviour.
The bottom line is that if you expect Hibernate to do it's job correctly, you HAVE to ensure that your in-memory model is correct. That means if you have mapped a bidirectional relationship (which you have), you have to create the memory references in both directions. This has nothing to do with one-to-one, it applies to relationships of any multiplicity.
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?
Dave,
It has nothing to do with Hibernate, ColdFusion, or anything beyond simple reference semantics. If you construct a graph of objects in memory where two objects both know about each other, then each one has to have a reference to the other object. There are two references. Consider this code:
barney = new Person();
barney.setName("Barney");
squeaker = new Pet();
squeaker.setName("Squeaker");
squeaker.setOwner(barney);
This code does NOT allow a Person to reference their Pet(s). It only allows Pets to reference their Owner. This is a one-directional relationship (from Pet to Person).
barney.addPet(squeaker);
With that, you can go from Person to Pet and from Pet to Person, which means that you have a bi-drectional (from Pet to Person and from Person to Pet).
If your domain model specifies a bi-directional relationship between two types and then you create some instances of those types but only wire up the references in one direction (the first code sample), your domain model is in an invalid state (i.e. you have a bug).
If your domain model is in an invalid state, then you're going to have problems. One of them happens to be that Hibernate can't figure out what to do, because you've told it one thing (the relationship is bidirectional) and then done something else (only constructed a reference in one direction). There are various other problems, but they're all the same root issue: you've failed to keep your domain model consistent with the invariants you defined to describe it.
To sum all that up, the bug is wholly in your (the developer) court. Hibernate is trying as best it can, but when you're telling it two different things, there's no way it can reliably pick the correct one.
Hibernate still won't pick up the correct one even if you set one of them with inverse="true"?
Dave,
There are two problems here. One: you want Hibernate to correctly persist your domain model when your domain model is in an invalid state. Two: you don't seem to think ensuring your domain model is valid is a worthy goal. Please don't think I'm picking on you in particular; this is a problem with pretty much anyone coming from SQL to state-based persistence. And since CFML developers are, in general, not coming from a hard-core CS background, the problem is exacerbated further.
If you simply ensure you domain model remains valid, all your problems will disappear. Well, maybe not all of them, but everything related to Hibernate persisting relationships correctly. If your domain model is in an invalid state, that problem needs to be addressed before anything else. The integrity of the domain model is the primary objective of every application.
Consider an automotive analogy. The domain model is the engine of your car, and Hibernate is the tires that let the engine move the car around. If your engine fails, it doesn't matter if you have a flat tire, because you can't drive anyway. You HAVE to fix the engine first.
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.
Check out my latest post: http://www.barneyb.com/barneyblog/2010/04/14/domain-model-integrity/ Hopefully it'll lend some more insight. But the gist of it is that you're not manipulating the database with your setters, you're manipulating objects in memory. The two are different in a whole pile of ways, and one of them is that in the database every relationship works in two directions, while in memory every reference works in one direction. It's not a downside, just a difference.
If you want to not think about it, don't use the built-in methods, but write your own. It's really simple:
property name="children" type="one-to-many" singularName="child";
function addChild(child) {
arrayAppend(children, child);
child.setParent(this);
}
This translates that "every relationship is two directional" semantic from the database into your memory model. Note that you'll need to do the same thing for removeChild. Unfortunately, CF doesn't provide a way to tell it to not generate the accessor methods for you, but such is life.
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()?
If you override both, you'll end up with mutual recursion (Parent.addChild calls Child.setParent calls Parent.addChild), so you have deal with that, either with package-access helper methods or some sort of "it's already set" check. And you need to do the same for removeChild. Any place one property gets manipulated you need to manipulate both, so if you add other business methods that do it, they need to handle it too (or just delegate to the property accessors).
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?
Yep, the "is it already set" check protects you from recursion. You'll probably want to do a similar thing in Parent.addChild() to avoid an extra lookup, but not strictly necessary.
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"?
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.
Dave,
Not to push you off, but I think it's time this migrate to the cf-orm-dev mailing list (http://groups.google.com/group/cf-orm-dev). That'll be a much better format discussing a specific issue like this. When you post, make sure you include the property definitions of your entities, along with your helper methods. Without those it's hard to say what's happening.
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!
Thanks! :-)
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.
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
Eric,
The Adobe docs are horribly confusing. The next sentence after the one you quoted is "For example, in ARTIST-ART relation, inverse should be set to true on the 'art' property in ARTIST." As you would certainly agree, in the ARTIST-ART association, the ARTIST is the "one" side and that's where they say you should set inverse="true".
For some reason they are trying to use "one" and "many" to reference the multiplicity of the property, rather than the number of objects. Still using ARTIST-ART, there is one ARTIST and many ARTs. The ART-to-ARTIST association is one-to-many. The inverse (ARTIST-to-ART) is many-to-one. However, the 'art' property on the ARTIST object is a collection.
Put in simpler terms, in a one-to-many relationship, the "one" side has a collection property and the "many" side has a non-collection property. Adobe is recommending the same thing, placing it on the "one" side (the side with the collection property). Why they have opted to use such counter-intuitive wording I have no idea.
Great post. After searching a lot ,Now i am able to understand it correctly.
Thanx. Keep doing good work.
Detail explanation. Thanks for your all effort.