hibernate-orm/reference/es/modules/example_parentchild.xml

362 lines
16 KiB
XML

<chapter id="example-parentchild">
<title>Ejemplo: Padre/Hijo</title>
<para>
Una de las primer&#x00ed;simas cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relaci&#x00f3;n de
tipo padre / hijo. Para esto hay dos enfoques diferentes. Por varias razones, el enfoque m&#x00e1;s conveniente,
especialmente para usuarios nuevos, es modelar tanto <literal>Parent</literal> como <literal>Child</literal>
como clases de entidad con una asociaci&#x00f3;n <literal>&lt;one-to-many&gt;</literal> desde <literal>Parent</literal>
a <literal>Child</literal>. (El enfoque alternativo es declarar el <literal>Child</literal> como un
<literal>&lt;composite-element&gt;</literal>.) Ahora, resulta que la sem&#x00e1;ntica por defecto de una asociaci&#x00f3;n
uno a muchos (en Hibernate) es mucho menos cercana a la sem&#x00e1;ntica usual de una relaci&#x00f3;n padre / hijo que aquellas
de un mapeo de elementos compuestos. Explicaremos c&#x00f3;mo usar una <emphasis>asociaci&#x00f3;n uno a muchos bidireccional
con tratamiento en cascada</emphasis> para modelar una relaci&#x00f3;n padre / hijo eficiente y elegantemente.
&#x00a1;No es para nada dif&#x00ed;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&#x00f3;gica de la entidad que las posee; nunca de
las entidades contenidas. &#x00a1;Esta es una distinci&#x00f3;n crucial! Esto tiene las siguientes consecuencias:
</para>
<itemizedlist>
<listitem>
<para>
Cuando se quita / a&#x00f1;ade un objeto desde / a una colecci&#x00f3;n, se incrementa el n&#x00fa;mero de versi&#x00f3;n del
due&#x00f1;o de la colecci&#x00f3;n.
</para>
</listitem>
<listitem>
<para>
Si un objeto que fue quitado de una colecci&#x00f3;n es una instancia de un tipo de valor (por ejemplo, un
elemento compuesto), ese objeta cesar&#x00e1; de ser persistente y su estado ser&#x00e1; completamente quitado de la
base de datos. Asimismo, a&#x00f1;adir una instancia de tipo de valor a la colecci&#x00f3;n causar&#x00e1; que su estado
sea inmediatamente persistente.
</para>
</listitem>
<listitem>
<para>
Por otro lado, si se quita una entidad de una colecci&#x00f3;n (una asociaci&#x00f3;n uno-a-muchos o muchos-a-muchos),
no ser&#x00e1; borrado, por defecto. Este comportamiento es completamente consistente. &#x00a1;Un cambio en el
estado interno de otra entidad no hace desaparecer la entidad asociada! Asimismo, a&#x00f1;adir una entidad a
una colecci&#x00f3;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&#x00f1;adir una entidad a una colecci&#x00f3;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&#x00f3;n padre / hijo. donde
la vida del hijo est&#x00e1; ligada al ciclo de vida del padre.
</para>
</sect1>
<sect1 id="example-parentchild-bidir">
<title>Uno-a-muchos bidirectional</title>
<para>
Sup&#x00f3;n que empezamos con una asociaci&#x00f3;n simple <literal>&lt;one-to-many&gt;</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&#x00e1;semos el siguiente c&#x00f3;digo
</para>
<programlisting><![CDATA[Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();]]></programlisting>
<para>
Hibernate publicar&#x00ed;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&#x00f3;lo ineficiente, sino que adem&#x00e1;s viola cualquier restricci&#x00f3;n <literal>NOT NULL</literal> en la
columna <literal>parent_id</literal>. Podemos reparar la violaci&#x00f3;n de restricci&#x00f3;n de nulabilidad
especificando <literal>not-null="true"</literal> en el mapeo de la colecci&#x00f3;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&#x00f3;n recomendada.
</para>
<para>
El caso subyacente de este comportamiento es que el enlace (la clave for&#x00e1;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&#x00f3;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&#x00e1;s a&#x00f1;adir la propiedad <literal>parent</literal> a la clase <literal>Child</literal>.)
</para>
<para>
Ahora que la entidad <literal>Child</literal> est&#x00e1; gestionando el estado del enlace, le decimos a la
colecci&#x00f3;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&#x00f3;digo podr&#x00ed;a ser usado para a&#x00f1;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, &#x00a1;S&#x00f3;lo se publicar&#x00ed;a un <literal>INSERT</literal> de SQL!
</para>
<para>
Para ajustar un poco m&#x00e1;s las cosas, podr&#x00ed;amos crear un m&#x00e9;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&#x00f3;digo para a&#x00f1;adir un <literal>Child</literal> se ve as&#x00ed;
</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&#x00ed;cita a <literal>save()</literal> es a&#x00fa;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&#x00f3;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&#x00f3;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&#x00e1; <literal>c</literal> de la base de datos; s&#x00f3;lo quitar&#x00e1; el enlace a <literal>p</literal>
(y causar&#x00e1; una violaci&#x00f3;n a una restricci&#x00f3;n <literal>NOT NULL</literal>). Necesitas borrar el hijo
expl&#x00ed;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&#x00f3;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&#x00f3;n especifique <literal>inverse="true"</literal>, el tratamiento en
cascada se procesa a&#x00fa;n al iterar los elementos de colecci&#x00f3;n. De modo que si requieres que un objeto sea
salvado, borrado o actualizado en cascada, debes a&#x00f1;adirlo a la colecci&#x00f3;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&#x00f3;n que hemos cargado un <literal>Parent</literal> en una <literal>Session</literal>, hemos hecho algunos
cambios en una acci&#x00f3;n de UI y deseamos hacer persistentes estos cambios en una nueva sesi&#x00f3;n llamando a
<literal>update()</literal>. El <literal>Parent</literal> contendr&#x00e1; una colecci&#x00f3;n de hijos y, ya que
est&#x00e1; habilitado el tratamiento en cascada, Hibernate necesita saber qu&#x00e9; hijos est&#x00e1;n reci&#x00e9;n instanciados
y cu&#x00e1;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&#x00e1; el identificador y el valor de la propiedad de versi&#x00f3;n/timestamp para determinar cu&#x00e1;les de
los hijos son nuevos. (Ver <xref linkend="objectstate-saveorupdate"/>.) <emphasis>En Hibernate3, no es
m&#x00e1;s necesario especificar un <literal>unsaved-value</literal> expl&#x00ed;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&#x00e1; muy bien para el caso de un identificador generado, pero &#x00bf;qu&#x00e9; de los
identificadores asignados y de los identificadores compuestos? Esto es m&#x00e1;s dif&#x00ed;cil, ya que Hibernate
no puede usar la propiedad identificadora para distinguir entre un objeto reci&#x00e9;n instanciado (con un
identificador asignado por el usuario) y un objeto cargado en una sesi&#x00f3;n previa. En este caso, Hibernate
bien usar&#x00e1; la propiedad de versi&#x00f3;n o timestamp, o bien consultar&#x00e1; realmente el cach&#x00e9; 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&#x00f3;n</title>
<para>
Hay que resumir un poco aqu&#x00ed; y podr&#x00ed;a parecer confuso a la primera vez. Sin embargo, en la pr&#x00e1;ctica,
todo funciona muy agradablemente. La mayor&#x00ed;a de las aplicaciones de Hibernate usan el patr&#x00f3;n
padre / hijo en muchos sitios.
</para>
<para>
Hemos mencionado una alternativa en el primer p&#x00e1;rrafo. Ninguno de los temas anteriores existe en el caso
de los mapeos <literal>&lt;composite-element&gt;</literal>, que tienen exactamente la sem&#x00e1;ntica de una
relaci&#x00f3;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 &#x00fa;nico.
</para>
</sect1>
</chapter>