En quoi JPA orphanRemoval = true diffère de la clause ON DELETE CASCADE DML


196

Je suis un peu confus au sujet de l' orphanRemovalattribut JPA 2.0 .

Je pense que je peux voir qu'il est nécessaire lorsque j'utilise les outils de génération de base de données de mon fournisseur JPA pour créer la base de données DDL sous-jacente pour avoir une ON DELETE CASCADErelation particulière.

Cependant, si la base de données existe et qu'elle a déjà une ON DELETE CASCADErelation sur la relation, n'est-ce pas suffisant pour mettre en cascade la suppression de manière appropriée? Que fait le orphanRemovalen plus?

À votre santé

Réponses:


302

orphanRemovaln'a rien à voir avec ON DELETE CASCADE.

orphanRemovalest une chose entièrement spécifique à l'ORM . Il marque l'entité "enfant" à supprimer lorsqu'elle n'est plus référencée à partir de l'entité "parent", par exemple lorsque vous supprimez l'entité enfant de la collection correspondante de l'entité parente.

ON DELETE CASCADEest une chose spécifique à la base de données , il supprime la ligne "enfant" dans la base de données lorsque la ligne "parent" est supprimée.


3
Cela signifie-t-il qu'ils ont un effet sûr, mais qu'un système différent est chargé de le faire?
Anonymoose

107
Anon, ça n'a pas le même effet. ON DELETE CASCADE indique au DB de supprimer tous les enregistrements enfants lorsque le parent est supprimé. C'est si je supprime la FACTURE, puis supprime tous les ITEMS sur cette FACTURE. OrphanRemoval indique à l'ORM que si je supprime un objet Item de la collection d'éléments qui appartiennent à un objet Invoice (en opération de mémoire), puis "enregistre" la facture, l'élément supprimé doit être supprimé de la base de données sous-jacente.
garyKeorkunian

3
Si vous utilisez une relation unidirectionnelle, l'orphelin sera automatiquement supprimé même si vous ne définissez pas orphanRemoval = true
Tim

105

Un exemple pris forme ici :

Lorsqu'un Employeeobjet entité est supprimé, l'opération de suppression est mise en cascade sur l' Addressobjet entité référencé . À cet égard, orphanRemoval=trueet cascade=CascadeType.REMOVEsont identiques, et si orphanRemoval=trueest spécifié, CascadeType.REMOVEest redondant.

La différence entre les deux paramètres réside dans la réponse à la déconnexion d'une relation. Par exemple, comme lors de la définition du champ d'adresse sur nullou sur un autre Addressobjet.

  • Si orphanRemoval=trueest spécifié, l' Addressinstance déconnectée est automatiquement supprimée. Ceci est utile pour nettoyer les objets dépendants (par exemple Address) qui ne devraient pas exister sans une référence d'un objet propriétaire (par exemple Employee).

  • Si seulement cascade=CascadeType.REMOVEest spécifié, aucune action automatique n'est entreprise car la déconnexion d'une relation n'est pas une opération de suppression.

Pour éviter de suspendre les références suite à la suppression des orphelins, cette fonctionnalité ne doit être activée que pour les champs contenant des objets dépendants privés non partagés.

J'espère que cela rend les choses plus claires.


Après avoir lu votre réponse, je me rends compte de la différence exacte entre les deux et mon problème a été résolu. Je suis resté coincé dans la suppression des entités enfants de la base de données, si celles-ci sont déconnectées (supprimées) de la collection définie dans l'entité parente. Pour la même chose, j'ai posé la question « stackoverflow.com/questions/15526440/… ». Je viens d'ajouter mon commentaire pour lier les deux questions.
Narendra Verma

@forhas s'il vous plaît passer par la question stackoverflow.com/questions/58185249/…
GokulRaj KN

46

Au moment où vous supprimez une entité enfant de la collection, vous supprimerez également cette entité enfant de la base de données. orphanRemoval implique également que vous ne pouvez pas changer de parent; s'il y a un service qui a des employés, une fois que vous retirez cet employé pour le mettre dans un autre service, vous aurez par inadvertance supprimé cet employé de la base de données lors du vidage / validation (ce qui vient en premier). Le moral est de régler orphanRemoval sur true tant que vous êtes certain que les enfants de ce parent ne migreront pas vers un parent différent tout au long de leur existence. L'activation de orphanRemoval ajoute également automatiquement REMOVE à la liste en cascade.


3
Exactement correct ... aussi appelé relation parent / enfant "privée".
HDave

Cela signifie que dès que j'appelle, department.remove(emp);cet employé sera supprimé de la table emp sans même appelercommit()
JavaTechnical

19

Le mappage JPA équivalent pour le DDL ON DELETE CASCADEest cascade=CascadeType.REMOVE. La suppression des orphelins signifie que les entités dépendantes sont supprimées lorsque la relation avec leur entité «parente» est détruite. Par exemple, si un enfant est supprimé d'une @OneToManyrelation sans le supprimer explicitement dans le gestionnaire d'entités.


1
cascade=CascadeType.REMOVEn'est PAS équivalent à ON DELETE CASCADE. Sur supprimer dans le code de l'application et n'affecte pas à DDL, les autres exécutés dans DB. Voir stackoverflow.com/a/19696859/548473
Grigory Kislin

12

La différence est:
- orphanRemoval = true: l'entité "enfant" est supprimée lorsqu'elle n'est plus référencée (son parent ne peut pas être supprimé).
- CascadeType.REMOVE: l'entité "Enfant" est supprimée uniquement lorsque son "Parent" est supprimé.


12

Comme il s'agit d'une question très courante, j'ai écrit cet article , sur lequel cette réponse est basée.

Transitions d'état d'entité

JPA traduit les transitions d'état d'entité en instructions SQL, telles que INSERT, UPDATE ou DELETE.

Transitions d'état d'entité JPA

Lorsque vous êtes persistune entité, vous planifiez l'exécution de l'instruction INSERT lorsque le EntityManagerest vidé, automatiquement ou manuellement.

lorsque vous êtes removeune entité, vous planifiez l'instruction DELETE, qui sera exécutée lorsque le contexte de persistance est vidé.

Transitions d'état d'entité en cascade

Pour plus de commodité, JPA vous permet de propager les transitions d'état d'entité des entités parentes vers les entités enfant.

Donc, si vous avez une Postentité parente qui a une @OneToManyassociation avec l' PostCommententité enfant:

Entités Post et PostComment

La commentscollection de l' Postentité est mappée comme suit:

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

CascadeType.ALL

L' cascadeattribut indique au fournisseur JPA de transmettre la transition d'état d'entité de l' Postentité parente à toutes les PostCommententités contenues dans la commentscollection.

Donc, si vous supprimez l' Postentité:

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

entityManager.remove(post);

Le fournisseur JPA supprimera d'abord l' PostCommententité, et lorsque toutes les entités enfants seront supprimées, il supprimera également l' Postentité:

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

DELETE FROM post WHERE id = 1

Élimination des orphelins

Lorsque vous définissez l' orphanRemovalattribut sur true, le fournisseur JPA va planifier une removeopération lorsque l'entité enfant est supprimée de la collection.

Donc, dans notre cas,

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

Le fournisseur JPA va supprimer l' post_commentenregistrement associé car l' PostCommententité n'est plus référencée dans la commentscollection:

DELETE FROM post_comment WHERE id = 1

SUR SUPPRIMER LA CASCADE

Le ON DELETE CASCADEest défini au niveau FK:

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

Une fois que vous faites cela, si vous supprimez une postligne:

DELETE FROM post WHERE id = 1

Toutes les post_commententités associées sont supprimées automatiquement par le moteur de base de données. Cependant, cela peut être une opération très dangereuse si vous supprimez une entité racine par erreur.

Conclusion

L'avantage du JPA cascadeet des orphanRemovaloptions est que vous pouvez également bénéficier d' un verrouillage optimiste pour éviter la perte de mises à jour .

Si vous utilisez le mécanisme de cascade JPA, vous n'avez pas besoin d'utiliser le niveau DDL ON DELETE CASCADE, ce qui peut être une opération très dangereuse si vous supprimez une entité racine qui a de nombreuses entités enfants à plusieurs niveaux.

Pour plus de détails sur ce sujet, consultez cet article .


Donc, dans la suppression des orphelins, une partie de votre réponse: post.getComments (). Remove (postComment); fonctionnera dans le mappage bidirectionnel OneToMany uniquement en raison de la cascade Persist. Sans cascade et sans suppression côté ManyToOne, comme dans votre exemple, la suppression de connexion entre 2 entités ne serait pas persistante dans DB?
aurelije

La suppression des orphelins n'est pas affectée par CascadeType. C'est un mécanisme complémentaire. Maintenant, vous confondez la suppression avec la persistance. La suppression des orphelins consiste à supprimer les associations non référencées tandis que la persistance consiste à enregistrer de nouvelles entités. Vous devez suivre les liens fournis dans la réponse pour mieux comprendre ces concepts.
Vlad Mihalcea

Je ne comprends pas une chose: comment la suppression des orphelins entrera-t-elle en jeu dans le mappage bidirectionnel si nous ne supprimons jamais la connexion du côté M? Je pense que supprimer PostComment de la liste de Post sans définir PostComment.post sur null n'entraînera pas la suppression de la connexion entre ces 2 entités dans DB. C'est pourquoi je pense que la suppression des orphelins ne se déclenchera pas, dans le monde relationnel, PostComment n'est pas orphelin. Je le testerai quand j'aurai du temps libre.
aurelije

1
J'ai ajouté ces deux exemples dans mon référentiel GitHub de persistance Java haute performance qui montre comment tout cela fonctionne. Vous n'avez pas besoin de synchroniser le côté enfant comme vous devez généralement le faire pour supprimer directement des entités. Cependant, la suppression des orphelins ne fonctionne que si la mise en cascade est ajoutée, mais cela semble être une limitation Hibernate, pas une spécification JPA.
Vlad Mihalcea

5

@GaryK réponse est absolument génial, je l' ai passé une heure à la recherche d'une explication par orphanRemoval = truerapport CascadeType.REMOVEet il m'a aidé à comprendre.

En résumé: orphanRemoval = truefonctionne de la même manière que CascadeType.REMOVE SEULEMENT SI nous supprimons object ( entityManager.delete(object)) et nous voulons que les objets enfants soient également supprimés.

Dans une situation complètement différente, lorsque nous récupérons des données comme List<Child> childs = object.getChilds()puis supprimons un enfant ( entityManager.remove(childs.get(0)) en utilisant orphanRemoval=true, l'entité correspondant à childs.get(0)sera supprimée de la base de données.


1
Vous avez une faute de frappe dans votre deuxième paragraphe: Il n'y a pas de méthode telle que entityManager.delete (obj); c'est entityManager.remove (obj).
JL_SO

3

la suppression des orphelins a le même effet que ON DELETE CASCADE dans le scénario suivant: - Disons que nous avons une simple relation plusieurs à un entre une entité étudiante et une entité guide, où de nombreux étudiants peuvent être mappés vers le même guide et dans la base de données, nous avons un relation de clé étrangère entre la table Student et Guide de sorte que la table Student ait id_guide comme 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;

// L'entité parente

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

Dans ce scénario, la relation est telle que l'entité étudiante est le propriétaire de la relation et, en tant que telle, nous devons sauvegarder l'entité étudiante afin de conserver l'ensemble du graphe d'objets, par exemple

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

Ici, nous mappons le même guide avec deux objets étudiants différents et puisque CASCADE.PERSIST est utilisé, le graphique d'objet sera enregistré comme ci-dessous dans la table de base de données (MySql dans mon cas)

Tableau ÉTUDIANT: -

ID Nom Département Id_Guide

1 Roy ECE 1

2 Nick ECE 1

Tableau GUIDE: -

ID NOM Salaire

1 Jean 1500 $

et maintenant si je veux supprimer l'un des étudiants, en utilisant

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

et lorsqu'un dossier d'étudiant est supprimé, l'enregistrement de guide correspondant doit également être supprimé, c'est là que l'attribut CASCADE.REMOVE dans l'entité Student entre en image et ce qu'il fait est; il supprime l'étudiant avec l'identifiant 1 ainsi que l'objet de guide correspondant (identifiant 1). Mais dans cet exemple, il y a un autre objet étudiant qui est mappé au même enregistrement de guide et à moins que nous n'utilisions l' attribut orphanRemoval = true dans l'entité de guide, le code de suppression ci-dessus ne fonctionnera pas.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.