aspose file tools*
The moose likes Object Relational Mapping and the fly likes Hibernate: unidirectional, one-to-many associations Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Spring in Action this week in the Spring forum!
JavaRanch » Java Forums » Databases » Object Relational Mapping
Bookmark "Hibernate: unidirectional, one-to-many associations" Watch "Hibernate: unidirectional, one-to-many associations" New topic
Author

Hibernate: unidirectional, one-to-many associations

Jeff Albertson
Ranch Hand

Joined: Sep 16, 2005
Posts: 1780
This post is about the hoops I jumped through in order to get Hibernate to set up a unidirectional, one-to-many association mapping to a java.util.List, something I thought would be common, and thus well-supported.

In Hibernate in Action, on page 232 I read:

Good uses for unidirectional one-to-many associations are uncommon in practice

Whoah! Here's my typical example of a one-to-many association:

table WHOLE
------------
WHOLE_ID <PK>
WHOLE_NAME

table PART
------------
PART_ID <PK>
WHOLE_ID <FK>
POSITION //0,1,2...
PART_NAME

I'd like Part to be an entity, not a component, because I'd like other entities to have FKs to PART. If I make the association bidirectional, then, because of 'inverse="true"', I can't use <list> to have a list of parts. As far as object navigation, I never need to write part.getWhole(), so I don't need bidirectionality anyway.

Nevertheless, if I follow what they're recommending and write the following mappings for a standard, bidirection one-to-many association:

<class name="Whole" table="WHOLE">
...
<set name="parts" cascade="all-delete-orphan" inverse="true">
<key column="WHOLE_ID"/>
<one-to-many class="Part" />
</set>
</class>

<class name="Part" table="PART">
...
<many-to-one name="whole" column="WHOLE_ID" class="Whole" not-null="true"/>
</class>

Everything works and inserting one whole with two parts generates three inserts, as expected:

Hibernate: insert into WHOLE (WHOLE_NAME) values (?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID) values (?, ?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID) values (?, ?)

And in table PART, column WHOLE_ID is NOT NULL, also as expected.

But if I drop the many-to-one element (and attribute inverse, of course), the SQL mutates to:

Hibernate: insert into WHOLE (WHOLE_NAME) values (?)
Hibernate: insert into PART (PART_NAME) values (?)
Hibernate: insert into PART (PART_NAME) values (?)
Hibernate: update PART set WHOLE_ID=? where PART_ID=?
Hibernate: update PART set WHOLE_ID=? where PART_ID=?

Ugh! insert-then-update?! And WHOLE_ID in no longer constrained to be NOT_NULL. Let me add that to the mapping and run the code again:

<class name="Whole" table="WHOLE">
...
<set name="parts" cascade="all-delete-orphan">
<key column="WHOLE_ID" not-null="true"/>
<one-to-many class="Part" />
</set>
</class>

This generates SQL:

Hibernate: insert into WHOLE (WHOLE_NAME) values (?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID) values (?,?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID) values (?,?)
Hibernate: update PART set WHOLE_ID=? where PART_ID=?
Hibernate: update PART set WHOLE_ID=? where PART_ID=?

At least FK WHOLE_ID is NOT NULL but the SQL is nuts! I wonder why it's updating? Lesson: if you're going to be representing the many-end of a many-to-one association with a set, you might as well make it bidirectional, at least from the viewpoint of the SQL it generates.

But back to my goal: I want to have a List of parts, to manage the POSITION column for me, which is a pain to maintain programmatically, as parts are added, removed and rearranged. My firt attempt at a mapping copies what I read on page 233:

<class name="Whole" table="WHOLE">
...
<list name="parts" cascade="all-delete-orphan">
<key column="WHOLE_ID" not-null="true"/>
<index column="POSITION"/>
<one-to-many class="Part" />
</list>
</class>

The SQL is still doing that insert-then-update:

Hibernate: insert into WHOLE (WHOLE_NAME) values (?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID, POSITION) values (?, ?, ?)
Hibernate: insert into PART (PART_NAME, WHOLE_ID, POSITION) values (?, ?, ?)
Hibernate: update PART set WHOLE_ID=?, POSITION=? where PART_ID=?
Hibernate: update PART set WHOLE_ID=?, POSITION=? where PART_ID=?

I can live with that, but I check the PART table and there is no unique index across the two columns WHOLE_ID, POSITION, as I would expect for a list.

How do you add a multi-column unique index via a mapping file? The only way I know to do this in Hibernate is to use a properties element with unique="true":

<class name="Whole" table="WHOLE">
...
<list name="parts" cascade="all-delete-orphan">
<key column="ORDER_ID" /> <!-- had to drop not-null="true" -->
<index column="POSITION"/>
<one-to-many class="Part" />
</list>
</class>

<class name="Part" table="PART">
<properties name="list-constraint" unique="true">
<property name="position" column="POSITION"/>
<property name="wholeId" column="WHOLE_ID"/>
</properties>
</class>

I tried to make POSITION and FK WHOLE_ID NOT NULL, but it generated runtime SQL exceptions because the code is doing that insert-then-update code, this time with null values At least there is a unique index on POSITION+WHOLE_ID, but look what I had to do to get it: introduce two Part properties I would have liked to avoid mentioning (I made their setters/getters private). Is there a better way to generate this unqiue index from the the mapping file? I'm mainly not happy about the fields being nullable.

Conclusion: you really have to want a list of parts! I thought this would be such a common situation that Hibernate would make it easy to specify, and I'm still not there (the nullable FK WHOLE_ID and POSITION). Any suggestions? Perhaps I should reconsider and make Part a component instead of an entity. I'll let you know how that goes...
[ January 19, 2006: Message edited by: Jeff Albrechtsen ]

There is no emoticon for what I am feeling!
Paul Sturrock
Bartender

Joined: Apr 14, 2004
Posts: 10336


In Hibernate in Action, on page 232 I read:

Good uses for unidirectional one-to-many associations are uncommon in practice

Whoah! Here's my typical example of a one-to-many association:

table WHOLE
------------
WHOLE_ID <PK>
WHOLE_NAME

table PART
------------
PART_ID <PK>
WHOLE_ID <FK>
POSITION //0,1,2...
PART_NAME


Yeah, the Hibernate documentation can be a little fundamentalist when it comes to prescribing the "correct" way to model relational data. That being said - I do struggle to come up with a real example of when I would want a PART to exist without its parent WHOLE.


JavaRanch FAQ HowToAskQuestionsOnJavaRanch
Jeff Albertson
Ranch Hand

Joined: Sep 16, 2005
Posts: 1780
Originally posted by Paul Sturrock:
Yeah, the Hibernate documentation can be a little fundamentalist when it comes to prescribing the "correct" way to model relational data. That being said - I do struggle to come up with a real example of when I would want a PART to exist without its parent WHOLE.


In fact, I am thinking of situations where a PART never exists without a parent WHOLE. Are you suggesting I make Part a component (instead of an entity) and use element <composite-element> in my mapping file?
Jeff Albertson
Ranch Hand

Joined: Sep 16, 2005
Posts: 1780
I've just noticed this example from the Hibernate Reference Documentation (7.4.1) for defining a one-to-many association that supports list:

Their comment is:

If you use a List (or other indexed collection) you need to set the key column of the foreign key to not null, and let Hibernate manage the association from the collections side to maintain the index of each element (making the other side virtually inverse by setting update="false" and insert="false")

This virtually inverse thing is new to me. I don't recall it from "Hibernate in Action", and I've never needed to set insert and update to false, so I've got to stare at this a bit more.

I ran the code, though, and it's still using insert-then-update and it still isn't setting a unqiue index on (WHOLE_ID, POSITION), so I can't say it's as good as what I would hand-code.
[ January 19, 2006: Message edited by: Jeff Albrechtsen ]
Yongjun jiao
Greenhorn

Joined: Apr 10, 2009
Posts: 2
regarding the insert-then-update pattern, you can add attribute update="false" to the <key> element inside you <set>; then the extra updates will be gone.
Bill Zelan
Ranch Hand

Joined: Jan 09, 2009
Posts: 46
the update="false" does not solve a thing. you just skip the update step.
the column will have the null value.
Yongjun jiao
Greenhorn

Joined: Apr 10, 2009
Posts: 2
update="false" as well as not-null="true" should resolve the problem
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Hibernate: unidirectional, one-to-many associations