Hibernate.org Community Documentation

- Anterior
- Siguiente

## Capítulo 24. Ejemplo: Padre/Hijo

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.

## 24.1. Nota sobre las colecciones

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.

## 24.2. Uno-a-muchos bidireccional

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();

## 24.3. Ciclo de vida en cascada

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()`.

## 24.4. Cascadas y `unsaved-value`

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 11.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.

## 24.5. Conclusión

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.

- AnteriorCapítulo 23. Additional modules
- Subir
- Inicio
- SiguienteCapítulo 25. Ejemplo: Aplicación de Weblog