362 lines
16 KiB
XML
362 lines
16 KiB
XML
<chapter id="example-parentchild">
|
|
<title>Ejemplo: Padre/Hijo</title>
|
|
|
|
<para>
|
|
Una de las primerísimas cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relación de
|
|
tipo padre / hijo. Para esto hay dos enfoques diferentes. Por varias razones, el enfoque más conveniente,
|
|
especialmente para usuarios nuevos, es modelar tanto <literal>Parent</literal> como <literal>Child</literal>
|
|
como clases de entidad con una asociación <literal><one-to-many></literal> desde <literal>Parent</literal>
|
|
a <literal>Child</literal>. (El enfoque alternativo es declarar el <literal>Child</literal> como un
|
|
<literal><composite-element></literal>.) Ahora, resulta que la semántica por defecto de una asociación
|
|
uno a muchos (en Hibernate) es mucho menos cercana a la semántica usual de una relación padre / hijo que aquellas
|
|
de un mapeo de elementos compuestos. Explicaremos cómo usar una <emphasis>asociación uno a muchos bidireccional
|
|
con tratamiento en cascada</emphasis> para modelar una relación padre / hijo eficiente y elegantemente.
|
|
¡No es para nada difícil!
|
|
</para>
|
|
|
|
<sect1 id="example-parentchild-collections">
|
|
<title>Una nota sobre las colecciones</title>
|
|
|
|
<para>
|
|
Se considera que las colecciones de Hibernate son una parte lógica de la entidad que las posee; nunca de
|
|
las entidades contenidas. ¡Esta es una distinción crucial! Esto tiene las siguientes consecuencias:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Cuando se quita / añade un objeto desde / a una colección, se incrementa el número de versión del
|
|
dueño de la colección.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Si un objeto que fue quitado de una colección es una instancia de un tipo de valor (por ejemplo, un
|
|
elemento compuesto), ese objeta cesará de ser persistente y su estado será completamente quitado de la
|
|
base de datos. Asimismo, añadir una instancia de tipo de valor a la colección causará que su estado
|
|
sea inmediatamente persistente.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Por otro lado, si se quita una entidad de una colección (una asociación uno-a-muchos o muchos-a-muchos),
|
|
no será borrado, por defecto. Este comportamiento es completamente consistente. ¡Un cambio en el
|
|
estado interno de otra entidad no hace desaparecer la entidad asociada! Asimismo, añadir una entidad a
|
|
una colección no causa que la entidad se vuelva persistente, por defecto.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
En cambio, el comportamiento por defecto es que al añadir una entidad a una colección se crea meramente
|
|
un enlace entre las dos entidades, mientras que al quitarla se quita el enlace. Esto es muy apropiado para
|
|
todos los tipos de casos. Donde no es para nada apropiado es en el caso de una relación padre / hijo. donde
|
|
la vida del hijo está ligada al ciclo de vida del padre.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-bidir">
|
|
<title>Uno-a-muchos bidirectional</title>
|
|
|
|
<para>
|
|
Supón que empezamos con una asociación simple <literal><one-to-many></literal> desde
|
|
<literal>Parent</literal> a <literal>Child</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Si ejecutásemos el siguiente código
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = .....;
|
|
Child c = new Child();
|
|
p.getChildren().add(c);
|
|
session.save(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate publicaría dos sentencias SQL:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>un <literal>INSERT</literal> para crear el registro de <literal>c</literal></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
un <literal>UPDATE</literal> para crear el enlace desde <literal>p</literal> a
|
|
<literal>c</literal>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Esto no es sólo ineficiente, sino que además viola cualquier restricción <literal>NOT NULL</literal> en la
|
|
columna <literal>parent_id</literal>. Podemos reparar la violación de restricción de nulabilidad
|
|
especificando <literal>not-null="true"</literal> en el mapeo de la colección:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children">
|
|
<key column="parent_id" not-null="true"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Sin embargo, esta no es la solución recomendada.
|
|
</para>
|
|
<para>
|
|
El caso subyacente de este comportamiento es que el enlace (la clave foránea <literal>parent_id</literal>)
|
|
de <literal>p</literal> a <literal>c</literal> no es considerado parte del estado del objeto
|
|
<literal>Child</literal> y por lo tanto no es creada en el <literal>INSERT</literal>. De modo que la
|
|
solución es hacer el enlace parte del mapeo del <literal>Child</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
|
|
|
<para>
|
|
(Necesitamos además añadir la propiedad <literal>parent</literal> a la clase <literal>Child</literal>.)
|
|
</para>
|
|
|
|
<para>
|
|
Ahora que la entidad <literal>Child</literal> está gestionando el estado del enlace, le decimos a la
|
|
colección que no actualice el enlace. Usamos el atributo <literal>inverse</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
El siguiente código podría ser usado para añadir un nuevo <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>
|
|
Y ahora, ¡Sólo se publicaría un <literal>INSERT</literal> de SQL!
|
|
</para>
|
|
|
|
<para>
|
|
Para ajustar un poco más las cosas, podríamos crear un método <literal>addChild()</literal> en
|
|
<literal>Parent</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public void addChild(Child c) {
|
|
c.setParent(this);
|
|
children.add(c);
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Ahora, el código para añadir un <literal>Child</literal> se ve así
|
|
</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>Ciclo de vida en cascada</title>
|
|
|
|
<para>
|
|
La llamada explícita a <literal>save()</literal> es aún molesta. Apuntaremos a esto usando tratamientos
|
|
en cascada.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Esto simplifica el código anterior a
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = new Child();
|
|
p.addChild(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Similarmente, no necesitamos iterar los hijos al salvar o borrar un <literal>Parent</literal>.
|
|
Lo siguiente quita <literal>p</literal> y todos sus hijos de la base de datos.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
session.delete(p);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Sin embargo, este código
|
|
</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>
|
|
no quitará <literal>c</literal> de la base de datos; sólo quitará el enlace a <literal>p</literal>
|
|
(y causará una violación a una restricción <literal>NOT NULL</literal>). Necesitas borrar el hijo
|
|
explícitamente llamando a <literal>delete()</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>
|
|
Ahora, en nuestro caso, un <literal>Child</literal> no puede existir realmente sin su padre. De modo que
|
|
si quitamos un <literal>Child</literal> de la colección, realmente queremos que sea borrado. Para esto,
|
|
debemos usar <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>
|
|
Nota: aunque el mapeo de la colección especifique <literal>inverse="true"</literal>, el tratamiento en
|
|
cascada se procesa aún al iterar los elementos de colección. De modo que si requieres que un objeto sea
|
|
salvado, borrado o actualizado en cascada, debes añadirlo a la colección. No es suficiente con simplemente
|
|
llamar a <literal>setParent()</literal>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-update">
|
|
<title>Tratamiento en cascada y <literal>unsaved-value</literal></title>
|
|
|
|
<para>
|
|
Supón que hemos cargado un <literal>Parent</literal> en una <literal>Session</literal>, hemos hecho algunos
|
|
cambios en una acción de UI y deseamos hacer persistentes estos cambios en una nueva sesión llamando a
|
|
<literal>update()</literal>. El <literal>Parent</literal> contendrá una colección de hijos y, ya que
|
|
está habilitado el tratamiento en cascada, Hibernate necesita saber qué hijos están recién instanciados
|
|
y cuáles representan filas existentes en la base de datos. Asumamos que tanto <literal>Parent</literal> como
|
|
<literal>Child</literal> tienen propiedades identificadoras generadas de tipo <literal>Long</literal>.
|
|
Hibernate usará el identificador y el valor de la propiedad de versión/timestamp para determinar cuáles de
|
|
los hijos son nuevos. (Ver <xref linkend="objectstate-saveorupdate"/>.) <emphasis>En Hibernate3, no es
|
|
más necesario especificar un <literal>unsaved-value</literal> explícitamente.</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>
|
|
Bueno, todo eso está muy bien para el caso de un identificador generado, pero ¿qué de los
|
|
identificadores asignados y de los identificadores compuestos? Esto es más difícil, ya que Hibernate
|
|
no puede usar la propiedad identificadora para distinguir entre un objeto recién instanciado (con un
|
|
identificador asignado por el usuario) y un objeto cargado en una sesión previa. En este caso, Hibernate
|
|
bien usará la propiedad de versión o timestamp, o bien consultará realmente el caché de segundo nivel,
|
|
o bien, en el peor de los casos, la base de datos, para ver si existe la fila.
|
|
</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>Conclusión</title>
|
|
|
|
<para>
|
|
Hay que resumir un poco aquí y podría parecer confuso a la primera vez. Sin embargo, en la práctica,
|
|
todo funciona muy agradablemente. La mayoría de las aplicaciones de Hibernate usan el patrón
|
|
padre / hijo en muchos sitios.
|
|
</para>
|
|
|
|
<para>
|
|
Hemos mencionado una alternativa en el primer párrafo. Ninguno de los temas anteriores existe en el caso
|
|
de los mapeos <literal><composite-element></literal>, que tienen exactamente la semántica de una
|
|
relación padre / hijo. Desafortunadamente, hay dos grandes limitaciones para las clases de elementos
|
|
compuestos: los elementos compuestos no pueden poseer sus propias colecciones, y no deben ser el hijo
|
|
de cualquier otra entidad que no sea su padre único.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |