¿Cómo difiere JPA orphanRemoval = true de la cláusula ON DELETE CASCADE DML

184

Estoy un poco confundido sobre el orphanRemovalatributo JPA 2.0 .

Creo que puedo ver que es necesario cuando uso las herramientas de generación de DB de mi proveedor de JPA para crear la base de datos subyacente DDL para tener una ON DELETE CASCADErelación particular.

Sin embargo, si el DB existe y ya tiene una ON DELETE CASCADErelación, ¿no es esto suficiente para poner en cascada la eliminación de manera adecuada? ¿Qué hace el orphanRemovalademás?

Salud

Markos Fragkakis
fuente

Respuestas:

292

orphanRemovalno tiene nada que ver con ON DELETE CASCADE.

orphanRemovales una cosa completamente específica de ORM . Marca la entidad "secundaria" que se eliminará cuando ya no se haga referencia a ella desde la entidad "principal", por ejemplo, cuando elimine la entidad secundaria de la colección correspondiente de la entidad principal.

ON DELETE CASCADEes una cosa específica de la base de datos , elimina la fila "secundaria" en la base de datos cuando se elimina la fila "principal".

axtavt
fuente
3
¿Significa esto que tienen el efecto seguro, pero un sistema diferente es responsable de hacer que suceda?
Anonymoose
101
Anon, no tiene el mismo efecto. ON DELETE CASCADE le dice a la base de datos que elimine todos los registros secundarios cuando se elimina el padre. Es decir, si elimino la FACTURA, elimino todos los ARTÍCULOS en esa FACTURA. OrphanRemoval le dice al ORM que si elimino un objeto Item de la colección de Artículos que pertenecen a un objeto Factura (en operación de memoria), y luego "guarda" la Factura, el Artículo eliminado debería eliminarse de la base de datos subyacente.
garyKeorkunian
2
Si usa una relación unidireccional, el huérfano se eliminará automáticamente aunque no configure huérfanoRemoval = true
Tim
98

Un ejemplo tomado aquí :

Cuando Employeese elimina un objeto de entidad, la operación de eliminación se conecta en cascada al Addressobjeto de entidad al que se hace referencia . En este sentido, orphanRemoval=truey cascade=CascadeType.REMOVEson idénticos, y si orphanRemoval=truese especifica, CascadeType.REMOVEes redundante.

La diferencia entre las dos configuraciones está en la respuesta a la desconexión de una relación. Por ejemplo, como cuando se configura el campo de dirección nulla otro Addressobjeto.

  • Si orphanRemoval=truese especifica, la Addressinstancia desconectada se elimina automáticamente. Esto es útil para limpiar objetos dependientes (por ejemplo Address) que no deberían existir sin una referencia de un objeto propietario (por ejemplo Employee).

  • Si solo cascade=CascadeType.REMOVEse especifica, no se realiza ninguna acción automática ya que desconectar una relación no es una operación de eliminación.

Para evitar referencias colgantes como resultado de la eliminación de huérfanos, esta función solo debe habilitarse para los campos que contienen objetos dependientes privados no compartidos.

Espero que esto lo aclare más.

forhas
fuente
Después de leer su respuesta, me doy cuenta de la diferencia exacta entre ambos y mi problema se ha resuelto. Me quedé atascado en la eliminación de las entidades secundarias de la base de datos, si están desconectadas (eliminadas) de la colección definida en la entidad principal. Por lo mismo hice la pregunta ' stackoverflow.com/questions/15526440/… '. Solo agrego mi comentario para vincular ambas preguntas.
Narendra Verma
@forhas por favor vaya a la pregunta stackoverflow.com/questions/58185249/…
GokulRaj KN
46

En el momento en que elimine una entidad secundaria de la colección, también eliminará esa entidad secundaria de la base de datos. orphanRemoval también implica que no puede cambiar de padres; Si hay un departamento que tiene empleados, una vez que elimine a ese empleado para colocarlo en otro departamento, habrá eliminado sin darse cuenta a ese empleado de la base de datos en flush / commit (lo que ocurra primero). La moral es establecer huérfanoRemoval en verdadero siempre que esté seguro de que los hijos de ese padre no migrarán a un padre diferente a lo largo de su existencia. Al activar huérfano, Removal también agrega automáticamente REMOVE a la lista en cascada.

Onur
fuente
3
Exactamente correcto ... también llamado una relación padre / hijo "privada".
HDave
Eso significa que tan pronto como me llamo department.remove(emp);ese empleado se elimina de la tabla emp sin ni siquiera llamarcommit()
JavaTechnical
18

El mapeo JPA equivalente para el DDL ON DELETE CASCADEes cascade=CascadeType.REMOVE. La eliminación huérfana significa que las entidades dependientes se eliminan cuando se destruye la relación con su entidad "principal". Por ejemplo, si un hijo se elimina de una @OneToManyrelación sin eliminarlo explícitamente en el administrador de la entidad.

Heri
fuente
1
cascade=CascadeType.REMOVENO es equivalente a ON DELETE CASCADE. Al eliminar en el código de la aplicación y no afecta a DDL, otro ejecutado en DB. Ver stackoverflow.com/a/19696859/548473
Grigory Kislin
9

La diferencia es:
- orphanRemoval = true: la entidad "Secundaria" se elimina cuando ya no se hace referencia a ella (su padre no se puede eliminar).
- CascadeType.REMOVE: la entidad "Secundaria" se elimina solo cuando se elimina su "Principal".

usuario3572554
fuente
6

Como esta es una pregunta muy común, escribí este artículo , en el que se basa esta respuesta.

Transiciones de estado de entidad

JPA traduce las transiciones de estado de la entidad a sentencias SQL, como INSERT, UPDATE o DELETE.

Transiciones de estado de entidad JPA

Cuando usted es persistuna entidad, está programando la instrucción INSERT para que se ejecute cuando EntityManagerse vacía, de forma automática o manual.

cuando eres removeuna entidad, estás programando la declaración DELETE, que se ejecutará cuando se vacíe el Contexto de persistencia.

Transiciones de estado de entidad en cascada

Por conveniencia, JPA le permite propagar las transiciones de estado de la entidad de las entidades principales a las secundarias.

Entonces, si tiene una Postentidad principal que tiene una @OneToManyasociación con la PostCommententidad secundaria:

Entidades Post y PostComment

La commentscolección en la Postentidad se asigna de la siguiente manera:

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

El cascadeatributo le dice al proveedor de JPA que pase la transición del estado de la Postentidad de la entidad principal a todas las PostCommententidades contenidas en elcomments colección.

Entonces, si elimina la Postentidad:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

El proveedor de JPA eliminará PostCommentprimero la entidad, y cuando se eliminen todas las entidades secundarias, también eliminará la Postentidad:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Retiro de huérfanos

Cuando establezca el orphanRemovalatributo en true, el proveedor de JPA programará una removeoperación cuando la entidad secundaria se elimine de la colección.

Entonces, en nuestro caso,

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

El proveedor de JPA eliminará el post_commentregistro asociado ya que la PostCommententidad ya no está referenciada en la commentscolección:

DELETE FROM post_comment WHERE id = 1

EN ELIMINAR CASCADA

El ON DELETE CASCADEse define en el nivel FK:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Una vez que haga eso, si elimina una postfila:

DELETE FROM post WHERE id = 1

El post_commentmotor de la base de datos elimina automáticamente todas las entidades asociadas . Sin embargo, esta puede ser una operación muy peligrosa si elimina una entidad raíz por error.

Conclusión

La ventaja de JPA cascadey las orphanRemovalopciones es que también puede beneficiarse del bloqueo optimista para evitar actualizaciones perdidas .

Si usa el mecanismo de cascada JPA, no necesita usar el nivel DDL ON DELETE CASCADE, que puede ser una operación muy peligrosa si elimina una entidad raíz que tiene muchas entidades secundarias en varios niveles.

Para obtener más detalles sobre este tema, consulte este artículo .

Vlad Mihalcea
fuente
Entonces, en Orphan Removal, parte de su respuesta: post.getComments (). Remove (postComment); funcionará en el mapeo bidireccional OneToMany solo por la cascada Persist. Sin la conexión en cascada y sin eliminación en el lado ManyToOne, como en su ejemplo, la eliminación de la conexión entre 2 entidades no se mantendría en DB.
aurelije
La eliminación de huérfanos no se ve afectada por CascadeType. Es un mecanismo complementario. Ahora, está confundiendo la eliminación con la persistencia. La eliminación de huérfanos se trata de eliminar asociaciones sin referencia, mientras que la persistencia se trata de salvar nuevas entidades. Debe seguir los enlaces proporcionados en la respuesta para comprender mejor estos conceptos.
Vlad Mihalcea
No entiendo una cosa: ¿cómo se activará la eliminación de huérfanos en el mapeo bidireccional si nunca eliminamos la conexión en el lado M? Creo que eliminar PostComment de la lista de Post sin establecer PostComment.post en nulo no dará como resultado la eliminación de la conexión entre esas 2 entidades en DB. Es por eso que creo que la eliminación de huérfanos no se activará, en el mundo relacional allí PostComment no es huérfano. Lo probaré cuando tenga algo de tiempo libre.
aurelije
1
Agregué estos dos ejemplos en mi repositorio GitHub de Java Persistence de alto rendimiento que demuestran cómo funciona todo. No es necesario que sincronice el lado secundario como suele hacer para eliminar entidades directamente. Sin embargo, la eliminación de huérfanos solo funciona si se agrega en cascada, pero eso parece ser una limitación de Hibernate, no una especificación JPA.
Vlad Mihalcea
5

@GaryK respuesta es absolutamente genial, he pasado una hora buscando una explicación orphanRemoval = truefrente CascadeType.REMOVEy me ayudó a entender.

En resumen: orphanRemoval = truefunciona de la misma manera que CascadeType.REMOVE SÓLO SI eliminamos object ( entityManager.delete(object)) y queremos que también se eliminen los objetos del niño.

En una situación completamente diferente, cuando busquemos algunos datos como List<Child> childs = object.getChilds()y luego eliminemos un child ( entityManager.remove(childs.get(0)) usando orphanRemoval=true, la entidad correspondiente childs.get(0)se eliminará de la base de datos.

pzeszko
fuente
1
Tiene un error tipográfico en su segundo párrafo: no existe un método como entityManager.delete (obj); es entityManager.remove (obj).
JL_SO
3

la eliminación huérfana tiene el mismo efecto que EN ELIMINAR CASCADA en el siguiente escenario: - Digamos que tenemos una relación simple de uno a uno entre la entidad estudiantil y una entidad guía, donde muchos estudiantes pueden asignarse a la misma guía y en la base de datos tenemos un relación de clave externa entre el alumno y la tabla de guía de modo que la tabla del alumno tenga id_guide como FK.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// La entidad principal

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

En este escenario, la relación es tal que la entidad estudiantil es la propietaria de la relación y, como tal, necesitamos guardar la entidad estudiantil para poder conservar todo el gráfico del objeto, por ejemplo

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

Aquí estamos mapeando la misma guía con dos objetos de estudiantes diferentes y dado que se utiliza CASCADE.PERSIST, el gráfico de objetos se guardará como se muestra a continuación en la tabla de la base de datos (MySql en mi caso)

Mesa ESTUDIANTE: -

ID Nombre Dept Id_Guide

1 Roy ECE 1

2 Nick ECE 1

Tabla de GUÍA: -

ID NOMBRE Salario

1 Juan $ 1500

y ahora si quiero eliminar a uno de los estudiantes, usando

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

y cuando se elimina un registro de estudiante, el registro de guía correspondiente también debe eliminarse, ahí es donde entra en escena el atributo CASCADE.REMOVE en la entidad de Estudiante y lo que hace es; elimina al estudiante con el identificador 1 y el objeto de guía correspondiente (identificador 1) Pero en este ejemplo, hay un objeto de estudiante más que se asigna al mismo registro de guía y, a menos que usemos el atributo orphanRemoval = true en la entidad de guía, el código de eliminación anterior no funcionará.

kunal
fuente