From: <tu...@us...> - 2003-04-26 21:22:44
|
Update of /cvsroot/hibernate/Hibernate2/doc/reference/src In directory sc8-pr-cvs1:/tmp/cvs-serv13979/src Modified Files: examples.xml Log Message: Added parent/child relationship example Index: examples.xml =================================================================== RCS file: /cvsroot/hibernate/Hibernate2/doc/reference/src/examples.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -C2 -d -r1.5 -r1.6 *** examples.xml 3 Mar 2003 01:07:03 -0000 1.5 --- examples.xml 26 Apr 2003 21:22:41 -0000 1.6 *************** *** 345,349 **** </sect1> ! </chapter> --- 345,711 ---- </sect1> ! ! <sect1 id="examples-s3"> ! <title>Parent/Child Relationship</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 bidirectional one-to-many ! association with cascades to model a parent/child relationship efficiently and elegantly. ! Its not at all difficult! ! </para> ! ! <sect2 id="examples-s3-1"> ! <title>Collections</title> ! ! <para> ! Hibernate collections are considered to be a logical part of their owning entity ! (in our example, the <literal>Parent</literal>); never of the contained entities ! (<literal>Child</literal> objects). This is a critical 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 without an independent lifecycle), 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 <emphasis>entity</emphasis> (a first class object with ! its own lifecycle) is removed from a collection (a one-to-many or ! many-to-many association), it will <emphasis>not</emphasis> 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 <emphasis>at all</emphasis> ! is the case of a parent/child relationship, where the life of the child is bound to the ! lifecycle of the parent even if the child is not a dependent component but a first class ! object. ! </para> ! ! </sect2> ! ! <sect2 id="examples-s3-2"> ! <title>Bidirectional one-to-many Associations</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 spacing="compact"> ! <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 parent_id column. ! </para> ! ! <para> ! The underlying cause is that the link (the foreign key constraint upon ! <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 (bidirectional). ! </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 Child 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. The attribute in the mapping used for ! that purpose is <literal>inverse</literal>. ! </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 this: ! </para> ! ! <programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid); ! Child c = new Child(); ! p.addChild(c); ! session.save(c); ! session.flush();]]></programlisting> ! ! </sect2> ! ! <sect2 id="examples-s3-3"> ! <title>Cascades</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 when using cascading operations: ! </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 <emphasis>not</emphasis> 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 Child since, by design, Hibernate does not have a garbage collector! Use ! </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> ! <emphasis>Note:</emphasis> 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> on the <literal>Child</literal> object. ! </para> ! ! </sect2> ! ! <sect2 id="examples-s3-4"> ! <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 ! <literal>Session</literal> (by calling <literal>update()</literal>). The ! <literal>Parent</literal> will contain a collection of children 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. ! </para> ! ! <para> ! The <literal>unsaved-value</literal> attribute is used to specify the identifier ! value of a newly instantiated instance. In Hibernate2, <literal>unsaved-value</literal> ! defaults to <literal>"null"</literal>, which is perfect for a <literal>Long</literal> ! identifier type. ! </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 spacing="compact"> ! <listitem> ! <para> ! set <literal>unsaved-value="none"</literal> and explicitly ! <literal>save()</literal> newly instantiated children ! </para> ! </listitem> ! <listitem> ! <para> ! set <literal>unsaved-value="any"</literal> and explicitly ! <literal>update()</literal> loaded children ! </para> ! </listitem> ! </itemizedlist> ! ! <para> ! before calling <literal>update(parent)</literal>. The first option is probably more ! sensible and so that is the default <literal>unsaved-value</literal> in Hibernate2. ! </para> ! ! <para> ! There is one further possibility. There is a new Interceptor 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 implements Lifecycle { ! private boolean _saved = false; ! public boolean onSave(Session s) { ! _saved=true; ! return NO_VETO; ! } ! public void onLoad(Session s, Serializable id) { ! _saved=true; ! } ! ...... ! public boolean isSaved() { ! return _saved; ! } ! }]]</programlisting> ! ! <para> ! And implement <literal>isUnsaved()</literal> ! </para> ! ! <programlisting><![CDATA[public Boolean isUnsaved(Object entity) { ! if (entity instanceof Persistent) { ! return new Boolean( !( (Persistent) entity ).isSaved() ); ! } ! else { ! return null; ! } ! }]]></programlisting> ! ! </sect2> ! ! <sect2 id="examples-s3-5"> ! <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 of this example. None of ! these issues exist in the case of <literal><composite-element></literal> ! mappings, which have <emphasis>exactly</emphasis> the semantics of a parent/child ! relationship. Unfortunately there are some severe limitations to composite element ! classes (at least in the current implementation). Composite elements may not own ! collections and may not be used in queries. Furthermore, they do not have surrogate ! primary keys, which is usually a much more flexible relational model. ! </para> ! </sect2> ! ! </sect1> ! </chapter> |