385 lines
16 KiB
XML
385 lines
16 KiB
XML
|
<chapter id="example-parentchild">
|
||
|
<title>Example: Parent/Child</title>
|
||
|
|
||
|
<para>
|
||
|
One of the very first things that new users try to do with Hibernate is to model a parent / child type
|
||
|
relationship. There are two different approaches to this. For various reasons the most convenient
|
||
|
approach, especially for new users, is to model both <literal>Parent</literal> and <literal>Child</literal>
|
||
|
as entity classes with a <literal><one-to-many></literal> association from <literal>Parent</literal>
|
||
|
to <literal>Child</literal>. (The alternative approach is to declare the <literal>Child</literal> as a
|
||
|
<literal><composite-element></literal>.) Now, it turns out that default semantics of a one to many
|
||
|
association (in Hibernate) are much less close to the usual semantics of a parent / child relationship than
|
||
|
those of a composite element mapping. We will explain how to use a <emphasis>bidirectional one to many
|
||
|
association with cascades</emphasis> to model a parent / child relationship efficiently and elegantly. It's
|
||
|
not at all difficult!
|
||
|
</para>
|
||
|
|
||
|
<sect1 id="example-parentchild-collections">
|
||
|
<title>A note about collections</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate collections are considered to be a logical part of their owning entity; never of the
|
||
|
contained entities. This is a crucial distinction! It has the following consequences:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
When we remove / add an object from / to a collection, the version number of the collection owner
|
||
|
is incremented.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
If an object that was removed from a collection is an instance of a value type (eg, a composite
|
||
|
element), that object will cease to be persistent and its state will be completely removed from
|
||
|
the database. Likewise, adding a value type instance to the collection will cause its state to be
|
||
|
immediately persistent.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
On the other hand, if an entity is removed from a collection (a one-to-many or many-to-many
|
||
|
association), it will not be deleted, by default. This behaviour is completely consistent - a
|
||
|
change to the internal state of another entity should not cause the associated entity to vanish!
|
||
|
Likewise, adding an entity to a collection does not cause that entity to become persistent, by
|
||
|
default.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Instead, the default behaviour is that adding an entity to a collection merely creates a link between
|
||
|
the two entities, while removing it removes the link. This is very appropriate for all sorts of cases.
|
||
|
Where it is not appropriate at all is the case of a parent / child relationship, where the life of the
|
||
|
child is bound to the lifecycle of the parent.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="example-parentchild-bidir">
|
||
|
<title>Bidirectional one-to-many</title>
|
||
|
|
||
|
<para>
|
||
|
Suppose we start with a simple <literal><one-to-many></literal> association from
|
||
|
<literal>Parent</literal> to <literal>Child</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="children">
|
||
|
<key column="parent_id"/>
|
||
|
<one-to-many class="Child"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
If we were to execute the following code
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = .....;
|
||
|
Child c = new Child();
|
||
|
p.getChildren().add(c);
|
||
|
session.save(c);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Hibernate would issue two SQL statements:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>an <literal>INSERT</literal> to create the record for <literal>c</literal></para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
an <literal>UPDATE</literal> to create the link from <literal>p</literal> to
|
||
|
<literal>c</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
This is not only inefficient, but also violates any <literal>NOT NULL</literal> constraint on the
|
||
|
<literal>parent_id</literal> column.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
The underlying cause is that the link (the foreign key <literal>parent_id</literal>) from
|
||
|
<literal>p</literal> to <literal>c</literal> is not considered part of the state of the <literal>Child</literal>
|
||
|
object and is therefore not created in the <literal>INSERT</literal>. So the solution is to make the link part
|
||
|
of the <literal>Child</literal> mapping.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
(We also need to add the <literal>parent</literal> property to the <literal>Child</literal> class.)
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Now that the <literal>Child</literal> entity is managing the state of the link, we tell the collection not
|
||
|
to update the link. We use the <literal>inverse</literal> attribute.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
||
|
<key column="parent_id"/>
|
||
|
<one-to-many class="Child"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
The following code would be used to add a new <literal>Child</literal>
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
Child c = new Child();
|
||
|
c.setParent(p);
|
||
|
p.getChildren().add(c);
|
||
|
session.save(c);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
And now, only one SQL <literal>INSERT</literal> would be issued!
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
To tighten things up a bit, we could create an <literal>addChild()</literal> method of
|
||
|
<literal>Parent</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public void addChild(Child c) {
|
||
|
c.setParent(this);
|
||
|
children.add(c);
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Now, the code to add a <literal>Child</literal> looks like
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
Child c = new Child();
|
||
|
p.addChild(c);
|
||
|
session.save(c);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="example-parentchild-cascades">
|
||
|
<title>Cascading lifecycle</title>
|
||
|
|
||
|
<para>
|
||
|
The explicit call to <literal>save()</literal> is still annoying. We will address this by
|
||
|
using cascades.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||
|
<key column="parent_id"/>
|
||
|
<one-to-many class="Child"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
This simplifies the code above to
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
Child c = new Child();
|
||
|
p.addChild(c);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Similarly, we don't need to iterate over the children when saving or deleting a <literal>Parent</literal>.
|
||
|
The following removes <literal>p</literal> and all its children from the database.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
session.delete(p);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
However, this code
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
Child c = (Child) p.getChildren().iterator().next();
|
||
|
p.getChildren().remove(c);
|
||
|
c.setParent(null);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
will not remove <literal>c</literal> from the database; it will ony remove the link to <literal>p</literal>
|
||
|
(and cause a <literal>NOT NULL</literal> constraint violation, in this case). You need to explicitly
|
||
|
<literal>delete()</literal> the <literal>Child</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
|
Child c = (Child) p.getChildren().iterator().next();
|
||
|
p.getChildren().remove(c);
|
||
|
session.delete(c);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Now, in our case, a <literal>Child</literal> can't really exist without its parent. So if we remove
|
||
|
a <literal>Child</literal> from the collection, we really do want it to be deleted. For this, we must
|
||
|
use <literal>cascade="all-delete-orphan"</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||
|
<key column="parent_id"/>
|
||
|
<one-to-many class="Child"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Note: even though the collection mapping specifies <literal>inverse="true"</literal>, cascades are still
|
||
|
processed by iterating the collection elements. So if you require that an object be saved, deleted or
|
||
|
updated by cascade, you must add it to the collection. It is not enough to simply call
|
||
|
<literal>setParent()</literal>.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="example-parentchild-update">
|
||
|
<title>Using cascading <literal>update()</literal></title>
|
||
|
|
||
|
<para>
|
||
|
Suppose we loaded up a <literal>Parent</literal> in one <literal>Session</literal>, made some changes in a UI
|
||
|
action and wish to persist these changes in a new Session (by calling <literal>update()</literal>). The
|
||
|
<literal>Parent</literal> will contain a collection of childen and, since cascading update is enabled, Hibernate
|
||
|
needs to know which children are newly instantiated and which represent existing rows in the database. Lets assume
|
||
|
that both <literal>Parent</literal> and <literal>Child</literal> have (synthetic) identifier properties of type
|
||
|
<literal>java.lang.Long</literal>. Hibernate will use the identifier property value to determine which of the
|
||
|
children are new. (You may also use the version or timestamp property, see
|
||
|
<xref linkend="manipulatingdata-updating-detached"/>.)
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
The <literal>unsaved-value</literal> attribute is used to specify the identifier value of a newly instantiated
|
||
|
instance. <literal>unsaved-value</literal> defaults to "null", which is perfect for a <literal>Long</literal>
|
||
|
identifier type. If we would have used a primitive identitifier property, we would need to specify
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<id name="id" type="long" unsaved-value="0">]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
for the <literal>Child</literal> mapping. (There is also an <literal>unsaved-value</literal> attribute
|
||
|
for version and timestamp property mappings.)
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
The following code will update <literal>parent</literal> and <literal>child</literal> and insert
|
||
|
<literal>newChild</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[//parent and child were both loaded in a previous session
|
||
|
parent.addChild(child);
|
||
|
Child newChild = new Child();
|
||
|
parent.addChild(newChild);
|
||
|
session.update(parent);
|
||
|
session.flush();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Well, thats all very well for the case of a generated identifier, but what about assigned identifiers
|
||
|
and composite identifiers? This is more difficult, since <literal>unsaved-value</literal> can't
|
||
|
distinguish between a newly instantiated object (with an identifier assigned by the user) and an object
|
||
|
loaded in a previous session. In these cases, you will probably need to give Hibernate a hint; either
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
define <literal>unsaved-value="null"</literal> or <literal>unsaved-value="negative"</literal>
|
||
|
on a <literal><version></literal> or <literal><timestamp></literal> property
|
||
|
mapping for the class.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
set <literal>unsaved-value="none"</literal> and explicitly <literal>save()</literal>
|
||
|
newly instantiated children before calling <literal>update(parent)</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
set <literal>unsaved-value="any"</literal> and explicitly <literal>update()</literal>
|
||
|
previously persistent children before calling <literal>update(parent)</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
<literal>none</literal> is the default <literal>unsaved-value</literal> for assigned and composite
|
||
|
identifiers.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
There is one further possibility. There is a new <literal>Interceptor</literal> method named
|
||
|
<literal>isUnsaved()</literal> which lets the application implement its own strategy for distinguishing
|
||
|
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public class Persistent {
|
||
|
private boolean _saved = false;
|
||
|
public void onSave() {
|
||
|
_saved=true;
|
||
|
}
|
||
|
public void onLoad() {
|
||
|
_saved=true;
|
||
|
}
|
||
|
......
|
||
|
public boolean isSaved() {
|
||
|
return _saved;
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
(The <literal>saved</literal> property is non-persistent.)
|
||
|
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||
|
and <literal>onSave()</literal> as follows.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||
|
if (entity instanceof Persistent) {
|
||
|
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||
|
}
|
||
|
else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean onLoad(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] state,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
|
||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean onSave(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] state,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
|
||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||
|
return false;
|
||
|
}]]></programlisting>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="example-parentchild-conclusion">
|
||
|
<title>Conclusion</title>
|
||
|
|
||
|
<para>
|
||
|
There is quite a bit to digest here and it might look confusing first time around. However, in practice, it
|
||
|
all works out quite nicely. Most Hibernate applications use the parent / child pattern in many places.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
We mentioned an alternative in the first paragraph. None of the above issues exist in the case of
|
||
|
<literal><composite-element></literal> mappings, which have exactly the semantics of a parent / child
|
||
|
relationship. Unfortunately, there are two big limitations to composite element classes: composite elements may
|
||
|
not own collections, and they should not be the child of any entity other than the unique parent. (However,
|
||
|
they <emphasis>may</emphasis> have a surrogate primary key, using an <literal><idbag></literal> mapping.)
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|