JPA: suppression unidirectionnelle plusieurs-à-un et en cascade

95

Disons que j'ai une relation unidirectionnelle @ManyToOne comme celle-ci:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;
}

@Entity
public class Child implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne
    @JoinColumn
    private Parent parent;  
}

Si j'ai un parent P et des enfants C 1 ... C n renvoyant à P, y a-t-il un moyen propre et joli dans JPA de supprimer automatiquement les enfants C 1 ... C n lorsque P est supprimé (ie entityManager.remove(P))?

Ce que je recherche, c'est une fonctionnalité similaire à ON DELETE CASCADESQL.

perp
la source
1
Même si seul 'Child' a une référence à 'Parent' (de cette façon, le référencement est unidirectionnel) est-il problématique pour vous d'ajouter la liste de 'Child' avec un mappage '@OneToMany' et l'attribut 'Cascade = ALL' à le parent'? Je suppose que JPA devrait résoudre ce problème, même si un seul camp détient la référence.
kvDennis
1
@kvDennis, il y a des cas où vous ne voulez pas coupler étroitement le côté multiple d'un côté. Par exemple, dans les configurations de type ACL où les autorisations de sécurité sont transparentes "add-on"
Bachi

Réponses:

73

Les relations dans JPA sont toujours unidirectionnelles, sauf si vous associez le parent à l'enfant dans les deux sens. Les opérations REMOVE en cascade du parent vers l'enfant nécessiteront une relation entre le parent et l'enfant (pas seulement l'inverse).

Vous devrez donc faire ceci:

  • Soit, changez la @ManyToOnerelation unidirectionnelle en une relation bidirectionnelle @ManyToOneou unidirectionnelle @OneToMany. Vous pouvez ensuite mettre en cascade les opérations REMOVE afin de EntityManager.removesupprimer le parent et les enfants. Vous pouvez également spécifier orphanRemovalcomme vrai, pour supprimer tous les enfants orphelins lorsque l'entité enfant de la collection parent est définie sur null, c'est-à-dire supprimer l'enfant lorsqu'il n'est présent dans aucune collection parent.
  • Ou, spécifiez la contrainte de clé étrangère dans la table enfant comme ON DELETE CASCADE. Vous devrez appeler EntityManager.clear()après l'appel EntityManager.remove(parent)car le contexte de persistance doit être actualisé - les entités enfants ne sont pas censées exister dans le contexte de persistance après avoir été supprimées dans la base de données.
Vineet Reynolds
la source
7
y a-t-il un moyen de faire No2 avec une annotation JPA?
user2573153
3
Comment faire No2 avec les mappages XML Hibernate?
arg20
92

Si vous utilisez hibernate comme fournisseur JPA, vous pouvez utiliser l'annotation @OnDelete. Cette annotation ajoutera à la relation le déclencheur ON DELETE CASCADE , qui délègue la suppression des enfants à la base de données.

Exemple:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}

Avec cette solution, une relation unidirectionnelle de l'enfant au parent suffit pour supprimer automatiquement tous les enfants. Cette solution ne nécessite aucun écouteur, etc. Une requête telle que DELETE FROM Parent WHERE id = 1 supprimera les enfants.

Thomas Hunziker
la source
4
Je ne peux pas le faire fonctionner de cette façon, existe-t-il une version spécifique de hibernate ou un autre exemple plus détaillé comme celui-ci?
Mardari
3
Il est difficile de dire pourquoi cela ne fonctionne pas pour vous. Pour que cela fonctionne, vous devrez peut-être régénérer le schéma ou ajouter manuellement la suppression en cascade. L'annotation @OnDelete semble être là depuis un certain temps en tant que telle, je ne devinerais pas que la version est un problème.
Thomas Hunziker
10
Merci d'avoir répondu. Note rapide: le déclencheur de cascade de base de données ne sera créé que si vous avez activé la génération DDL via la mise en veille prolongée. Sinon, vous devrez l'ajouter d'une autre manière (par exemple liquibase) pour permettre aux requêtes ad hoc de s'exécuter directement sur la base de données comme 'DELETE FROM Parent WHERE id = 1' effectuer la suppression en cascade.
mjj1409
1
cela ne fonctionne pas quand l'association est @OneToOneDes idées avec comment le résoudre @OneToOne?
stakowerflol
1
@ThomasHunziker cela ne fonctionnera pas pour orphanRemoval, n'est-ce pas?
oxyt
13

Créez une relation bidirectionnelle, comme ceci:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
tekumara
la source
8
mauvaise réponse, les relations bidirectionnelles sont terribles dans JPA car opérer sur de grands ensembles pour enfants prend un temps incroyable
Enerccio
1
Y a-t-il une preuve que les relations bidirectionnelles sont lentes?
shalama
@enerccio Que faire si la relation bidirectionnelle est un à un? Veuillez également montrer un article indiquant que les relations bidirectionnelles sont lentes? lent en quoi? récupération? suppression? mise à jour?
saran3h
@ saran3h chaque opération (ajouter, supprimer) chargera tous les enfants, donc c'est une énorme charge de données qui peut être inutile (comme l'ajout d'une valeur ne nécessite pas de charger tous les enfants de la base de données, ce qui est exactement ce que fait ce mappage).
Enerccio
@Enerccio Je pense que tout le monde utilise le chargement paresseux sur les jointures. Alors, comment est-ce toujours un problème de performance?
saran3h
1

J'ai vu dans @ManytoOne unidirectionnel, supprimer ne fonctionne pas comme prévu. Lorsque le parent est supprimé, idéalement l'enfant devrait également être supprimé, mais seul le parent est supprimé et l'enfant n'est PAS supprimé et reste orphelin

Les technologies utilisées sont Spring Boot / Spring Data JPA / Hibernate

Démarrage du sprint: 2.1.2.RELEASE

Spring Data JPA / Hibernate est utilisé pour supprimer la ligne .eg

parentRepository.delete(parent)

ParentRepository étend le référentiel CRUD standard comme indiqué ci-dessous ParentRepository extends CrudRepository<T, ID>

Voici ma classe d'entité

@Entity(name = child”)
public class Child  {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne( fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = parent_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Parent parent;
}

@Entity(name = parent”)
public class Parent {

    @Id
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 50)
    private String firstName;


}
ranjesh
la source
J'ai trouvé la solution pour laquelle la suppression ne fonctionnait pas. Apparemment, hibernate n'utilisait PAS mysql Engine -INNODB, vous avez besoin du moteur INNODB pour que mysql génère une contrainte de clé étrangère. En utilisant les propriétés suivantes dans application.properties, rend Spring Boot / Hibernate pour utiliser le moteur mysql INNODB. Donc, la contrainte de clé étrangère fonctionne et donc supprime également la cascade
ranjesh
Les propriétés manquées sont utilisées dans le commentaire précédent. Voici les propriétés de printemps utiliséesspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh
FYI, vous vous êtes trompé "dans le code. Voirname= "parent"
alexander
0

Utilisez cette méthode pour supprimer un seul côté

    @ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;
Shubham
la source
-1

@Cascade (org.hibernate.annotations.CascadeType.DELETE_ORPHAN)

Étant donné l'annotation a fonctionné pour moi. Peut avoir un essai

Par exemple :-

     public class Parent{
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            @Column(name="cct_id")
            private Integer cct_id;
            @OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
            @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
            private List<Child> childs;
        }
            public class Child{
            @ManyToOne(fetch=FetchType.EAGER)
            @JoinColumn(name="cct_id")
            private Parent parent;
    }
Swarit Agarwal
la source