JPA OneToMany no elimina hijo

158

Tengo un problema con una @OneToManyasignación simple entre una entidad principal y una entidad secundaria. Todo funciona bien, solo que los registros secundarios no se eliminan cuando los elimino de la colección.

El padre:

@Entity
public class Parent {
    @Id
    @Column(name = "ID")
    private Long id;

    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
    private Set<Child> childs = new HashSet<Child>();

 ...
}

El niño:

@Entity
public class Child {
    @Id
    @Column(name = "ID")
    private Long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="PARENTID", nullable = false)
    private Parent parent;

  ...
}

Si ahora elimino un elemento secundario del conjunto de elementos secundarios, no se eliminará de la base de datos. Intenté anular la child.parentreferencia, pero eso tampoco funcionó.

Las entidades se utilizan en una aplicación web, la eliminación ocurre como parte de una solicitud de Ajax. No tengo una lista de niños eliminados cuando se presiona el botón Guardar, por lo que no puedo eliminarlos implícitamente.

bert
fuente

Respuestas:

253

El comportamiento de JPA es correcto (significado , según la especificación ): los objetos no se eliminan simplemente porque los ha eliminado de una colección OneToMany. Hay extensiones específicas del proveedor que hacen eso, pero JPA nativo no lo atiende.

En parte, esto se debe a que JPA no sabe si debería eliminar algo eliminado de la colección. En términos de modelado de objetos, esta es la diferencia entre composición y "agregación *".

En composición , la entidad hija no tiene existencia sin el padre. Un ejemplo clásico es entre Casa y Habitación. Eliminar la casa y las habitaciones van también.

Agregación es un tipo de asociación más flexible y se caracteriza por Curso y Estudiante. Elimine el curso y el alumno aún existe (probablemente en otros cursos).

Por lo tanto, debe usar extensiones específicas del proveedor para forzar este comportamiento (si está disponible) o eliminar explícitamente el elemento secundario Y eliminarlo de la colección principal.

Soy consciente de:

cletus
fuente
Gracias la buena explicación. así es como lo temía. (Hice un poco de búsqueda / lectura antes de preguntar, solo queriendo ser guardado). de alguna manera empiezo a lamentar la decisión de usar JPA API y nit Hibernate directamente. Intentaré el puntero Chandra Patni y usaré el tipo de cascada hibernate delete_orphan.
bert
Tengo un tipo de pregunta similar sobre esto. Sería amable echar un vistazo a esta publicación aquí, por favor. stackoverflow.com/questions/4569857/…
Thang Pham
78
Con JPA 2.0, ahora puede usar la opción orphanRemoval = true
itsadok
2
Gran explicación inicial y buenos consejos sobre OrphanRemoval. No tenía idea de que JPA no tenía en cuenta este tipo de eliminación. Los matices entre lo que sé sobre Hibernate y lo que JPA realmente hace pueden ser enloquecedores.
sma
¡Bien explicado al exponer las diferencias entre Composición y Agregación!
Felipe Leão
73

Además de la respuesta de cletus, JPA 2.0 , final desde diciembre de 2010, introduce un orphanRemovalatributo en las @OneToManyanotaciones. Para más detalles vea esta entrada del blog .

Tenga en cuenta que, dado que la especificación es relativamente nueva, no todos los proveedores de JPA 1 tienen una implementación final de JPA 2. Por ejemplo, la versión Hibernate 3.5.0-Beta-2 aún no admite este atributo.

Louis Jacomet
fuente
entrada de blog: el enlace está roto.
Steffi
1
Y la magia de la máquina del camino nos salva: web.archive.org/web/20120225040254/http://javablog.co.uk/2009/…
Louis Jacomet
43

Puedes probar esto:

@OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true).

Maciel Bombonato
fuente
2
Gracias. Pero la pregunta ha vuelto de la JPA 1 veces. Y esta opción no estaba disponible desde entonces.
bert
9
Es bueno saber, sin embargo, para aquellos de nosotros que buscamos una solución para esto en los tiempos de JPA2 :)
alex440
20

Como se explicó, no es posible hacer lo que quiero con JPA, así que utilicé la anotación hibernate.cascade, con esto, el código relevante en la clase Parent ahora se ve así:

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "parent")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
            org.hibernate.annotations.CascadeType.DELETE,
            org.hibernate.annotations.CascadeType.MERGE,
            org.hibernate.annotations.CascadeType.PERSIST,
            org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<Child> childs = new HashSet<Child>();

No podría simplemente usar 'ALL' ya que esto también habría eliminado al padre.

bert
fuente
4

Aquí cascada, en el contexto de eliminar, significa que los hijos se eliminan si eliminas al padre. No es la asociación. Si está utilizando Hibernate como su proveedor JPA, puede hacerlo utilizando la cascada específica de hibernate .

Chandra Patni
fuente
4

Puedes probar esto:

@OneToOne(cascade = CascadeType.REFRESH) 

o

@OneToMany(cascade = CascadeType.REFRESH)
Amit Ghorpade
fuente
3
@Entity 
class Employee {
     @OneToOne(orphanRemoval=true)
     private Address address;
}

Ver aquí .

Chang
fuente