Borrar hijos en un OneToMany

Cuando tienes un OneToMany con JoinColumn, cómo haces para borrar los hijos desde el padre. Por ejemplo, hay una relación uno a muchos entre un Autor y los Libros que ha escrito o entre un Post y los Comentarios que tiene. ¿Cómo hago para desconectar y luego borrar un Comentario del Post al que pertenece?

Este curso ha sido marcado como anticuado y no está siendo revisado de forma activa. Es posible que la información pueda estar desactualizada o que los enlaces se hayan roto.

Recordemos la situación que nos plantea la gestión de relaciones OneToMany en JPA, donde tenemos una entidad principal que contiene una colección de entidades relacionadas. Por ejemplo, un blog con publicaciones, y cada publicación con comentarios asociados. El reto surge cuando queremos eliminar uno o varios de esos comentarios dentro de la misma transacción, asegurándonos de que no queden registros huérfanos en la base de datos.

Para entender mejor este escenario, pensemos en una publicación que tiene varios comentarios. Si queremos eliminar un comentario específico, no basta con simplemente quitarlo de la colección en la entidad publicación y poner a null la referencia desde el comentario hacia la publicación. Esto es porque, sin una configuración adecuada, JPA solo actualizará la clave foránea a null, dejando el comentario en la base de datos sin asociación, es decir, huérfano.

Aquí es donde entra en juego la anotación orphanRemoval. Al marcar la relación OneToMany con orphanRemoval = true, le indicamos a JPA que los objetos hijos (en este caso, los comentarios) no pueden existir sin su padre (la publicación). Por lo tanto, si un comentario se elimina de la colección de la publicación, JPA debe eliminar también ese comentario de la base de datos. Esto evita que queden registros huérfanos y mantiene la integridad del modelo.

Veamos un ejemplo simplificado de cómo se define esta relación en la entidad publicación:

@Entity
public class Publicacion {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String texto;

    @OneToMany(mappedBy = "publicacion", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comentario> comentarios = new ArrayList<>();

    // getters y setters

    public void agregarComentario(Comentario comentario) {
        comentarios.add(comentario);
        comentario.setPublicacion(this);
    }

    public void eliminarComentario(Comentario comentario) {
        comentarios.remove(comentario);
        comentario.setPublicacion(null);
    }
}

Y la entidad comentario:

@Entity
public class Comentario {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String texto;

    @ManyToOne
    @JoinColumn(name = "publicacion_id")
    private Publicacion publicacion;

    // getters y setters
}

En este diseño, cuando llamamos a eliminarComentario, quitamos el comentario de la lista y desasociamos la publicación. Gracias a orphanRemoval = true, JPA detecta que ese comentario ya no está asociado a ninguna publicación y lo elimina automáticamente de la base de datos al confirmar la transacción.

Es importante destacar que el atributo cascade y orphanRemoval tienen roles diferentes. El cascade define qué operaciones se propagan desde el padre a los hijos, por ejemplo, si borramos una publicación, que se borren también sus comentarios. Sin embargo, cascade no elimina automáticamente un hijo cuando simplemente lo quitamos de la colección; para eso necesitamos orphanRemoval.

Otra cuestión a tener en cuenta es que la implementación concreta de JPA que usemos puede afectar el comportamiento. Por ejemplo, en pruebas con proveedores como Hibernate o EclipseLink, hemos visto que algunos requieren anotaciones adicionales para que las eliminaciones en cascada funcionen correctamente. Esto puede generar confusión y problemas inesperados, especialmente en operaciones de borrado, donde cada proveedor puede tener sus peculiaridades o bugs.

Por último, si queremos que los comentarios puedan existir sin estar asociados a una publicación, no deberíamos usar orphanRemoval ni una clave foránea obligatoria. En ese caso, sería más adecuado usar una tabla intermedia (join table) para la relación, evitando claves foráneas nulas que violarían las reglas de normalización en la base de datos.

En resumen, para eliminar hijos en una relación OneToMany dentro de la misma transacción y evitar registros huérfanos, la clave está en usar orphanRemoval = true junto con una correcta gestión de la asociación en ambas entidades. Además, debemos ser conscientes de las diferencias entre proveedores JPA y probar cuidadosamente nuestro código para asegurarnos de que el comportamiento es el esperado.

Lista de reproducción
  1. 1
    La persistencia es clave
    6 minutos
  2. 2
    Instalando Hibernate
    8 minutos
  3. 3
    Crear el persistence.xml
    9 minutos
  4. 4
    Construyendo una Entity
    8 minutos
  5. 5
    Accediendo al EntityManager
    9 minutos
  6. 6
    Insertando con persist
    7 minutos
  7. 7
    Managed Entities
    6 minutos
  8. 8
    Merge y remove
    5 minutos
  9. 9
    Inciso sobre Java 8
    7 minutos
  10. 10
    Introducción a relaciones
    6 minutos
  11. 11
    Relación OneToOne
    11 minutos
  12. 12
    OneToOne inverso con mappedBy
    8 minutos
  13. 13
    OneToMany: planteamiento
    6 minutos
  14. 14
    OneToMany: anotaciones
    6 minutos
  15. 15
    OneToMany: EntityManager
    7 minutos
  16. 16
    ¡AYUDA! Error Lazy Initialization (OneToMany)
    5 minutos
  17. 17
    ¡AYUDA! No se fijan las relaciones (OneToMany)
    8 minutos
  18. 18
    Borrar hijos en un OneToMany
    11 minutos
  19. 19
    ON DELETE SET NULL
    8 minutos