Thursday, January 19, 2012

Hibernate: Mapping Parent/Child Relationship

Overview:
This post will look at how you map bi-directional one-to-many association. It's one of the most common association types that exists, and the semantics in mapping the association with cascade the parent/child relationship.

Relationship:

1 0..m
| Parent | ----------------- | Child |

Database tables:

CREATE TABLE Parent(
parent_id INTEGER NOT NULL,
PRIMARY KEY (parent_id)
);
CREATE TABLE Child(
child_id INTEGER NOT NULL,
parent_id INTEGER NOT NULL,
PRIMARY KEY (child_id),
FOREIGN KEY (parent_id) REFERENCES Parent
);

POJOs:

package org.fazlan;

public class Parent {
private long id;
private Set children = new HashSet();
// getters and setters
}

package org.fazlan;

public class Child {
private long id;
private Parent parent;
// getters and setters
}

Hibernate Mapping:
Lets look at the basic mapping the parent and child entities,


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.fazlan">
<class name="Parent" table="parent">
. . .
<set name="children" inverse="true">
  <key column="parent_id" />
  <one-to-many class"Child" />
</set>
</class>

<class name="Child" table="child">
. . .
<many-to-one name="parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping>
Now that the Child entity is managing the state of the link(parent_id), and we declare the Child entity is the one that manages the relationship. We use the 'inverse' attribute to do this.

The following code is used to add a Parent,
Parent p1 = new Parent();  session.save(p1);  session.flush();
SQL generated by Hibernate,
Hibernate: select max(parent_id) from hibernatedb.Parent
Hibernate: insert into hibernatedb.Parent (parent_id) values (?)
The following code is used to add a Child,
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
Alternatively, you can add the following method to parent.

public void addChild(Child c) {
c.setParent(this);
getChildren().add(c);
}
Updated code will look like this,
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();
NOTE:
  • When inverse="false" (default), Hibernate will generate TWO SQL statements: INSERT to create the child. UPDATE to create the link between parent and child.
<set name="children" inverse="false" >
<key column="parent_id" />
<one-to-many class"Child" />
</set>
SQL generated by Hibernate,
Hibernate: insert into hibernatedb.Child (parent_id, child_id) values (?, ?)
Hibernate: update hibernatedb.Child set parent_id=? where child_id=?
  • When inverse="true", Hibernate generates ONLY ONE SQL statement: INSERT the child and the parent's id.
<set name="children" inverse="true" >
<key column="parent_id" />
<one-to-many class"Child" />
</set>
SQL generated by Hibernate,
Hibernate: insert into hibernatedb.Child (parent_id, child_id) values (?, ?)
Using Cascade associations:
You can get rid of the explicit call to the save() by using cascase attribute to the collection mapping.
<set name="children" inverse="true" cascase="all">
<key column="parent_id" />
<one-to-many class"Child" />
</set>
Simplified code,
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();
This will insert the new child to the database. Also, the following code will now delete the parent and all it's children.
Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();
However, the following will not remove c from the database. In this case, it will only remove the link to p and cause a NOT NULL constraint violation.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c); // remove link
c.setParent(null); // NOT NULL constraint violation
session.flush(); 
to achieve this, you need to explicitly call the delete() on the Child.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
But, a Child cannot exist without its parent. So if we remove a Child from the collection, we do want it to be deleted. To do this, we must use cascade="all-delete-orphan".
<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
Updated code, Now, removing the child from the collection would also will result in removing the child from the database
Parent p1 = (Parent) session.get(Parent.class,1l);
Child c1 = p1.getChildren().iterator().next();
p1.getChildren().remove(c1);
session.flush();
Summary:
This post looked at how you can map Parent/Child relationship efficiently in Hibernate, and also briefly looked at the 'inverse' and 'cascade' features.

3 comments:

  1. Grate article, short and sweet....

    Keep it up mate

    ReplyDelete
  2. Hi
    Can you please help me in
    http://stackoverflow.com/questions/18895585/hibernate-version-annotation-and-object-references-an-unsaved-transient-instanc

    ReplyDelete
  3. I want to update "chilrden" ?
    =>parent must update List
    and children mus update parent
    Im only thinking How to do it ?

    ReplyDelete