361 lines
15 KiB
XML
361 lines
15 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. We can fix the nullability constraint violation by specifying
|
|
<literal>not-null="true"</literal> in the collection mapping:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children">
|
|
<key column="parent_id" not-null="true"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
However, this is not the recommended solution.
|
|
</para>
|
|
<para>
|
|
The underlying cause of this behaviour 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>Cascades and <literal>unsaved-value</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 genenerated
|
|
identifier properties of type <literal>Long</literal>. Hibernate will use the identifier and
|
|
version/timestamp property value to determine which of the children are new. (See
|
|
<xref linkend="objectstate-saveorupdate"/>.) <emphasis>In Hibernate3, it is no longer necessary to specify
|
|
an <literal>unsaved-value</literal> explicitly.</emphasis>
|
|
</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 Hibernate can't use the identifier property to
|
|
distinguish between a newly instantiated object (with an identifier assigned by the user) and an
|
|
object loaded in a previous session. In this case, Hibernate will either use the timestamp or version
|
|
property, or will actually query the second-level cache or, worst case, the database, to see if the
|
|
row exists.
|
|
</para>
|
|
|
|
<!-- undocumenting
|
|
<para>
|
|
There is one further possibility. The <literal>Interceptor</literal> method named
|
|
<literal>isUnsaved()</literal> 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>
|
|
|
|
<para>
|
|
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
|
</para>
|
|
-->
|
|
</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 very 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.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |