JPA OneToMany ne supprime pas l'enfant

158

J'ai un problème avec un simple @OneToManymappage entre un parent et une entité enfant. Tout fonctionne bien, seuls les enregistrements enfants ne sont pas supprimés lorsque je les supprime de la collection.

Le parent:

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

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

 ...
}

L'enfant:

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

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

  ...
}

Si je supprime maintenant un enfant de l'ensemble childs, il n'est pas supprimé de la base de données. J'ai essayé d'annuler la child.parentréférence, mais cela n'a pas fonctionné non plus.

Les entités sont utilisées dans une application web, la suppression intervient dans le cadre d'une requête Ajax. Je n'ai pas de liste d'enfants supprimés lorsque le bouton Enregistrer est enfoncé, je ne peux donc pas les supprimer implicitement.

Bert
la source

Réponses:

253

Le comportement de JPA est correct (c'est- à- dire selon la spécification ): les objets ne sont pas supprimés simplement parce que vous les avez supprimés d'une collection OneToMany. Il existe des extensions spécifiques au fournisseur qui le font, mais JPA natif ne le prend pas en charge.

Ceci est en partie dû au fait que JPA ne sait pas vraiment s'il doit supprimer quelque chose de supprimé de la collection. En termes de modélisation d'objets, c'est la différence entre composition et «agrégation *.

En composition , l'entité enfant n'a pas d'existence sans le parent. Un exemple classique est entre House et Room. Supprimez la maison et les pièces disparaissent également.

L'agrégation est un type d'association plus souple et se caractérise par le cours et l'étudiant. Supprimez le cours et l'étudiant existe toujours (probablement dans d'autres cours).

Vous devez donc utiliser des extensions spécifiques au fournisseur pour forcer ce comportement (le cas échéant) ou supprimer explicitement l'enfant ET le supprimer de la collection parent.

Je suis conscient de:

cletus
la source
merci la belle explication. c'est donc ce que je craignais. (J'ai fait quelques recherches / lectures avant de demander, voulant juste être sauvé). D'une manière ou d'une autre, je commence à regretter la décision d'utiliser directement l'API JPA et nit Hibernate .. Je vais essayer le pointeur Chandra Patni et utiliser le type de cascade delete_orphan hibernate.
bert
J'ai une sorte de question similaire à ce sujet. Serait-il gentil de jeter un œil à ce post ici, s'il vous plaît? stackoverflow.com/questions/4569857/…
Thang Pham
78
Avec JPA 2.0, vous pouvez désormais utiliser l'option orphanRemoval = true
itsadok
2
Excellente explication initiale et bons conseils sur orphanRemoval. Je n'avais aucune idée que JPA n'avait pas pris en compte ce type de suppression. Les nuances entre ce que je sais d'Hibernate et ce que fait réellement JPA peuvent être exaspérantes.
sma
Bien expliqué en exposant les différences entre composition et agrégation!
Felipe Leão
73

En plus de la réponse de cletus, JPA 2.0 , définitif depuis décembre 2010, introduit un orphanRemovalattribut sur les @OneToManyannotations. Pour plus de détails, consultez cette entrée de blog .

Notez que puisque la spécification est relativement nouvelle, tous les fournisseurs JPA 1 n'ont pas une implémentation finale de JPA 2. Par exemple, la version Hibernate 3.5.0-Beta-2 ne prend pas encore en charge cet attribut.

Louis Jacomet
la source
entrée de blog - le lien est rompu.
Steffi
1
Et la magie de la machine de retour nous sauve: web.archive.org/web/20120225040254/http://javablog.co.uk/2009/…
Louis Jacomet le
43

Vous pouvez essayer ceci:

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

Maciel Bombonato
la source
2
Merci. Mais la question est revenue de l'APJ 1 fois. Et cette option n'était pas disponible à l'époque.
bert
9
bon à savoir quand même, pour ceux d'entre nous qui recherchent une solution à ce problème à l'époque JPA2 :)
alex440
20

Comme expliqué, il n'est pas possible de faire ce que je veux avec JPA, j'ai donc utilisé l'annotation hibernate.cascade, avec ceci, le code correspondant dans la classe Parent ressemble maintenant à ceci:

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

Je ne pouvais pas simplement utiliser «TOUS» car cela aurait également supprimé le parent.

Bert
la source
4

Ici, la cascade, dans le contexte de remove, signifie que les enfants sont supprimés si vous supprimez le parent. Pas l'association. Si vous utilisez Hibernate comme fournisseur JPA, vous pouvez le faire en utilisant une cascade spécifique à Hibernate .

Chandra Patni
la source
4

Vous pouvez essayer ceci:

@OneToOne(cascade = CascadeType.REFRESH) 

ou

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

Regardez ici .

Chang
la source