Hibernate.orgCommunity Documentation
Una de las primeras cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relación de tipo padre / hijo. Para esto existen dos enfoques diferentes. El enfoque más conveniente, especialmente para los usuarios nuevos, es modelar tanto Parent
como Child
como clases de entidad con una asociación <one-to-many>
desde Parent
a Child
. El enfoque opcional es declarar el Child
como un <composite-element>
. La semántica prederterminada 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 la de un mapeo de elementos compuestos. Explicaremos cómo utilizar una asociación uno-a-muchos bidireccional con tratamiento en cascada para modelar una relación padre / hijo de manera eficiente y elegante.
Se considera que las colecciones de Hibernate son una parte lógica de la entidad que las posee y no de las entidades contenidas. Note que esta es una diferencia crucial y que esto tiene las siguientes consecuencias:
Cuando se elimina/agrega un objeto desde/a una colección, se incrementa el número de la versión del dueño de la colección.
Si un objeto que fue eliminado de una colección es una instancia de un tipo de valor (por ejemplo, un elemento compuesto), ese objeto cesará de ser persistente y su estado será completamente eliminado de la base de datos. Asimismo, añadir una instancia de tipo de valor a la colección causará que su estado sea persistente inmediatamente.
Por otro lado, si se elimina una entidad de una colección (una asociación uno-a-muchos o muchos-a-muchos), no se borrará por defecto. Este comportamiento es completamente consistente; un cambio en el estado interno de otra entidad no hace desaparecer la entidad asociada. Asimismo, el agregar una entidad a una colección no causa que la entidad se vuelva persistente por defecto.
El comportamiento por defecto es que al agregar una entidad a una colección se crea un enlace entre las dos entidades. Al eliminar la entidad se eliminará el enlace. Esto es muy apropiado para todos los tipos de casos. Sin embargo, no apropiado en el caso de una relación padre / hijo. En este caso la vida del hijo se encuentra vinculada al ciclo de vida del padre.
Supónga que empezamos con una asociación simple <one-to-many>
desde Parent
a Child
.
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
Si ejecutásemos el siguiente código:
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();
Hibernate publicaría dos declaraciones SQL:
un INSERT
para crear el registro de c
un UPDATE
para crear el enlace desde p
a c
Esto no es sólo ineficiente, sino que además viola cualquier restricción NOT NULL
en la columna parent_id
. Puede arreglar la violación de restricción de nulabilidad especificando not-null="true"
en el mapeo de la colección:
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set
>
Sin embargo, esta no es la solución recomendada.
El caso subyacente de este comportamiento es que el enlace (la clave foránea parent_id
) de p
a c
no se considera parte del estado del objeto Child
y por lo tanto no se crea en el INSERT
. De modo que la solución es hacer que el enlace sea parte del mapeo del Child
.
<many-to-one name="parent" column="parent_id" not-null="true"/>
También necesita agregar la propiedad parent
a la clase Child
.
Ahora que la entidad Child
está administrando el estado del enlace, le decimos a la colección que no actualice el enlace. Usamos el atributo inverse
para hacer esto:
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
El siguiente código se podría utilizar para agregar un nuevo Child
:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
Sólo se emitiría un INSERT
de SQL.
También podría crear un método addChild()
de Parent
.
public void addChild(Child c) {
c.setParent(this);
children.add(c);
}
El código para agregar un Child
se ve así:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();
Puede abordar las frustraciones de la llamada explícita a save()
utilizando cascadas.
<set name="children" inverse="true" cascade="all">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
Esto simplifica el código anterior a:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();
De manera similar, no necesitamos iterar los hijos al guardar o borrar un Parent
. Lo siguiente elimina p
y todos sus hijos de la base de datos.
Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();
Sin embargo, el siguiente código:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();
no eliminará c
de la base de datos. En este caso, sólo quitará el enlace a p
y causará una violación a una restricción NOT NULL
. Necesita borrar el hijo explícitamente llamando a delete()
en Child
.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
En nuestro caso, un Child
no puede existir realmente sin su padre. De modo que si eliminamos un Child
de la colección, realmente queremos que sea borrado. Para esto, tenemos que utilizar cascade="all-delete-orphan"
.
<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set
>
Aunque el mapeo de la colección especifique inverse="true"
, el tratamiento en cascada se procesa aún al iterar los elementos de la colección. De modo que si necesita que un objeto se guarde, borre o actualice en cascada, debe añadirlo a la colección. No es suficiente con simplemente llamar a setParent()
.
Suppose we loaded up a Parent
in one Session
, made some changes in a UI action and wanted to persist these changes in a new session by calling update()
. The Parent
will contain a collection of children and, since the cascading update is enabled, Hibernate needs to know which children are newly instantiated and which represent existing rows in the database. We will also assume that both Parent
and Child
have generated identifier properties of type Long
. Hibernate will use the identifier and version/timestamp property value to determine which of the children are new. (See Sección 10.7, “Detección automática de estado”.) In Hibernate3, it is no longer necessary to specify an unsaved-value
explicitly.
El siguiente código actualizará parent
y child
e insertará newChild
:
//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();
Todo eso es apropiado 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 utilizará la propiedad de versión o sello de fecha, o bien consultará realmente el caché de segundo nivel, o bien, en el peor de los casos, consultará la base de datos, para ver si la fila existe.
Las secciones que acabamos de cubrir pueden parecer un poco confusas. Sin embargo, en la práctica, todo funciona muy bien. La mayoría de las aplicaciones de Hibernate utilizan el patrón padre / hijo en muchos sitios.
Mencionamos una opción en el primer párrafo. Ninguno de los temas anteriores existe en el caso de los mapeos <composite-element>
, los cuales tienen exactamente la semántica de una relación padre / hijo. Desafortunadamente, existen 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.
Copyright © 2004 Red Hat, Inc.