1
<?xml version='1.0' encoding="UTF-8"?>
2
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
4
<chapter id="example-parentchild">
5
<title>Example: Parent/Child</title>
8
One of the very first things that new users try to do with Hibernate is to model a parent / child type
9
relationship. There are two different approaches to this. For various reasons the most convenient
10
approach, especially for new users, is to model both <literal>Parent</literal> and <literal>Child</literal>
11
as entity classes with a <literal><one-to-many></literal> association from <literal>Parent</literal>
12
to <literal>Child</literal>. (The alternative approach is to declare the <literal>Child</literal> as a
13
<literal><composite-element></literal>.) Now, it turns out that default semantics of a one to many
14
association (in Hibernate) are much less close to the usual semantics of a parent / child relationship than
15
those of a composite element mapping. We will explain how to use a <emphasis>bidirectional one to many
16
association with cascades</emphasis> to model a parent / child relationship efficiently and elegantly.
17
It's not at all difficult!
20
<sect1 id="example-parentchild-collections">
21
<title>A note about collections</title>
24
Hibernate collections are considered to be a logical part of their owning entity; never of the
25
contained entities. This is a crucial distinction! It has the following consequences:
31
When we remove / add an object from / to a collection, the version number of the collection owner
37
If an object that was removed from a collection is an instance of a value type (eg, a composite
38
element), that object will cease to be persistent and its state will be completely removed from
39
the database. Likewise, adding a value type instance to the collection will cause its state to be
40
immediately persistent.
45
On the other hand, if an entity is removed from a collection (a one-to-many or many-to-many
46
association), it will not be deleted, by default. This behaviour is completely consistent - a
47
change to the internal state of another entity should not cause the associated entity to vanish!
48
Likewise, adding an entity to a collection does not cause that entity to become persistent, by
55
Instead, the default behaviour is that adding an entity to a collection merely creates a link between
56
the two entities, while removing it removes the link. This is very appropriate for all sorts of cases.
57
Where it is not appropriate at all is the case of a parent / child relationship, where the life of the
58
child is bound to the life cycle of the parent.
63
<sect1 id="example-parentchild-bidir">
64
<title>Bidirectional one-to-many</title>
67
Suppose we start with a simple <literal><one-to-many></literal> association from
68
<literal>Parent</literal> to <literal>Child</literal>.
71
<programlisting><![CDATA[<set name="children">
72
<key column="parent_id"/>
73
<one-to-many class="Child"/>
74
</set>]]></programlisting>
77
If we were to execute the following code
80
<programlisting><![CDATA[Parent p = .....;
81
Child c = new Child();
82
p.getChildren().add(c);
84
session.flush();]]></programlisting>
87
Hibernate would issue two SQL statements:
92
<para>an <literal>INSERT</literal> to create the record for <literal>c</literal></para>
96
an <literal>UPDATE</literal> to create the link from <literal>p</literal> to
103
This is not only inefficient, but also violates any <literal>NOT NULL</literal> constraint on the
104
<literal>parent_id</literal> column. We can fix the nullability constraint violation by specifying
105
<literal>not-null="true"</literal> in the collection mapping:
108
<programlisting><![CDATA[<set name="children">
109
<key column="parent_id" not-null="true"/>
110
<one-to-many class="Child"/>
111
</set>]]></programlisting>
114
However, this is not the recommended solution.
117
The underlying cause of this behaviour is that the link (the foreign key <literal>parent_id</literal>)
118
from <literal>p</literal> to <literal>c</literal> is not considered part of the state of the
119
<literal>Child</literal> object and is therefore not created in the <literal>INSERT</literal>. So the
120
solution is to make the link part of the <literal>Child</literal> mapping.
123
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
126
(We also need to add the <literal>parent</literal> property to the <literal>Child</literal> class.)
130
Now that the <literal>Child</literal> entity is managing the state of the link, we tell the collection
131
not to update the link. We use the <literal>inverse</literal> attribute.
134
<programlisting><![CDATA[<set name="children" inverse="true">
135
<key column="parent_id"/>
136
<one-to-many class="Child"/>
137
</set>]]></programlisting>
140
The following code would be used to add a new <literal>Child</literal>
143
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
144
Child c = new Child();
146
p.getChildren().add(c);
148
session.flush();]]></programlisting>
151
And now, only one SQL <literal>INSERT</literal> would be issued!
155
To tighten things up a bit, we could create an <literal>addChild()</literal> method of
156
<literal>Parent</literal>.
159
<programlisting><![CDATA[public void addChild(Child c) {
162
}]]></programlisting>
165
Now, the code to add a <literal>Child</literal> looks like
168
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
169
Child c = new Child();
172
session.flush();]]></programlisting>
176
<sect1 id="example-parentchild-cascades">
177
<title>Cascading life cycle</title>
180
The explicit call to <literal>save()</literal> is still annoying. We will address this by
184
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
185
<key column="parent_id"/>
186
<one-to-many class="Child"/>
187
</set>]]></programlisting>
190
This simplifies the code above to
193
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
194
Child c = new Child();
196
session.flush();]]></programlisting>
199
Similarly, we don't need to iterate over the children when saving or deleting a <literal>Parent</literal>.
200
The following removes <literal>p</literal> and all its children from the database.
203
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
205
session.flush();]]></programlisting>
211
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
212
Child c = (Child) p.getChildren().iterator().next();
213
p.getChildren().remove(c);
215
session.flush();]]></programlisting>
218
will not remove <literal>c</literal> from the database; it will ony remove the link to <literal>p</literal>
219
(and cause a <literal>NOT NULL</literal> constraint violation, in this case). You need to explicitly
220
<literal>delete()</literal> the <literal>Child</literal>.
223
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
224
Child c = (Child) p.getChildren().iterator().next();
225
p.getChildren().remove(c);
227
session.flush();]]></programlisting>
230
Now, in our case, a <literal>Child</literal> can't really exist without its parent. So if we remove
231
a <literal>Child</literal> from the collection, we really do want it to be deleted. For this, we must
232
use <literal>cascade="all-delete-orphan"</literal>.
235
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
236
<key column="parent_id"/>
237
<one-to-many class="Child"/>
238
</set>]]></programlisting>
241
Note: even though the collection mapping specifies <literal>inverse="true"</literal>, cascades are
242
still processed by iterating the collection elements. So if you require that an object be saved,
243
deleted or updated by cascade, you must add it to the collection. It is not enough to simply call
244
<literal>setParent()</literal>.
249
<sect1 id="example-parentchild-update">
250
<title>Cascades and <literal>unsaved-value</literal></title>
253
Suppose we loaded up a <literal>Parent</literal> in one <literal>Session</literal>, made some changes
254
in a UI action and wish to persist these changes in a new session by calling <literal>update()</literal>.
255
The <literal>Parent</literal> will contain a collection of childen and, since cascading update is enabled,
256
Hibernate needs to know which children are newly instantiated and which represent existing rows in the
257
database. Lets assume that both <literal>Parent</literal> and <literal>Child</literal> have genenerated
258
identifier properties of type <literal>Long</literal>. Hibernate will use the identifier and
259
version/timestamp property value to determine which of the children are new. (See
260
<xref linkend="objectstate-saveorupdate"/>.) <emphasis>In Hibernate3, it is no longer necessary to specify
261
an <literal>unsaved-value</literal> explicitly.</emphasis>
265
The following code will update <literal>parent</literal> and <literal>child</literal> and insert
266
<literal>newChild</literal>.
269
<programlisting><![CDATA[//parent and child were both loaded in a previous session
270
parent.addChild(child);
271
Child newChild = new Child();
272
parent.addChild(newChild);
273
session.update(parent);
274
session.flush();]]></programlisting>
277
Well, that's all very well for the case of a generated identifier, but what about assigned identifiers
278
and composite identifiers? This is more difficult, since Hibernate can't use the identifier property to
279
distinguish between a newly instantiated object (with an identifier assigned by the user) and an
280
object loaded in a previous session. In this case, Hibernate will either use the timestamp or version
281
property, or will actually query the second-level cache or, worst case, the database, to see if the
287
There is one further possibility. The <literal>Interceptor</literal> method named
288
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
289
newly instantiated objects. For example, you could define a base class for your persistent classes.
292
<programlisting><![CDATA[public class Persistent {
293
private boolean _saved = false;
294
public void onSave() {
297
public void onLoad() {
301
public boolean isSaved() {
304
}]]></programlisting>
307
(The <literal>saved</literal> property is non-persistent.)
308
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
309
and <literal>onSave()</literal> as follows.
312
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
313
if (entity instanceof Persistent) {
314
return new Boolean( !( (Persistent) entity ).isSaved() );
321
public boolean onLoad(Object entity,
324
String[] propertyNames,
327
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
331
public boolean onSave(Object entity,
334
String[] propertyNames,
337
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
339
}]]></programlisting>
342
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
347
<sect1 id="example-parentchild-conclusion">
348
<title>Conclusion</title>
351
There is quite a bit to digest here and it might look confusing first time around. However, in practice,
352
it all works out very nicely. Most Hibernate applications use the parent / child pattern in many places.
356
We mentioned an alternative in the first paragraph. None of the above issues exist in the case of
357
<literal><composite-element></literal> mappings, which have exactly the semantics of a parent / child
358
relationship. Unfortunately, there are two big limitations to composite element classes: composite elements
359
may not own collections, and they should not be the child of any entity other than the unique parent.