Comment supprimer entity with ManyToMany relationship dans JPA (et les lignes de jointure correspondantes)?

disons que j'ai deux entités: groupe et utilisateur. Chaque utilisateur peut être membre de plusieurs groupes et chaque groupe peut avoir de nombreux utilisateurs.

@Entity
public class User {
    @ManyToMany
    Set<Group> groups;
    //...
}

@Entity
public class Group {
    @ManyToMany(mappedBy="groups")
    Set<User> users;
    //...
}

Maintenant, je veux supprimer un groupe (disons qu'il a beaucoup de membres).

le problème est que quand J'appelle EntityManager.remove() sur certains groupes, fournisseur JPA (dans mon cas, Hibernate) ne pas supprimer des lignes de la table de jointure et de supprimer l'opération échoue en raison de contraintes de clé étrangère. Appeler remove () sur User works fine (je suppose que cela a quelque chose à voir avec posséder côté de la relation).

Alors, comment puis-je supprimer un groupe dans ce cas?

seul moyen que je pourrais venir avec est de charger tous les utilisateurs dans le groupe, puis pour chaque utilisateur Supprimer groupe courant de ses groupes et mettre à jour l'utilisateur. Mais il me semble ridicule d'appeler update () sur chaque utilisateur du groupe juste pour pouvoir supprimer ce groupe.

71
demandé sur rdk 2009-07-04 16:13:00

7 réponses

  • la propriété de la relation est déterminée par l'endroit où vous placez l'attribut "mappedBy" à l'annotation. L'entité que vous mettez "mappedBy" est celle qui n'est pas le propriétaire. Il n'y a aucune chance pour les deux camps d'être propriétaires. Si vous n'avez pas de cas d'utilisation' Supprimer l'utilisateur', vous pouvez simplement déplacer la propriété vers l'entité Group , comme actuellement le User est le propriétaire.
  • d'un autre côté, vous n'avez pas demandé à ce sujet, mais une chose vaut la peine savoir. Les numéros groups et users ne sont pas combinés. Je veux dire, après avoir supprimé l'instance User1 du Groupe 1.utilisateurs, L'Utilisateur1.groupes collections n'est pas changé automatiquement (ce qui est assez surprenant pour moi),
  • en somme, je vous suggère de décider qui est le propriétaire. Disons que le User est le propriétaire. Ensuite, lors de la suppression d'un utilisateur, la relation user-group sera mise à jour automatiquement. Mais lors de la suppression d'un groupe vous devez prendre soin de supprimer le relation vous-même comme ceci:

entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()
65
répondu Grzegorz Oledzki 2009-07-06 20:15:32

les travaux suivants pour moi. Ajoutez la méthode suivante à l'entité qui n'est pas le propriétaire de la relation (Groupe)

@PreRemove
private void removeGroupsFromUsers() {
    for (User u : users) {
        u.getGroups().remove(this);
    }
}

gardez à l'esprit que pour que cela fonctionne, le groupe doit avoir une liste mise à jour des utilisateurs (ce qui n'est pas fait automatiquement). ainsi, à chaque fois que vous ajoutez un groupe à la liste des groupes dans l'entité utilisateur, vous devriez aussi ajouter un utilisateur à la liste des utilisateurs dans l'entité groupe.

36
répondu Damian 2013-02-16 15:30:22

j'ai trouvé une solution possible, mais... Je ne sais pas si c'est une bonne solution.

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Role_id"),
            inverseJoinColumns=@JoinColumn(name="Permission_id")
        )
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

@Entity
public class Permission extends Identifiable {

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Permission_id"),
            inverseJoinColumns=@JoinColumn(name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

j'ai essayé ceci et cela fonctionne. Lorsque vous supprimez le rôle, les relations sont également supprimées (mais pas les entités de Permission) et lorsque vous supprimez la Permission, les relations avec le rôle sont également supprimées (mais pas l'instance de rôle). Mais nous cartographions une relation unidirectionnelle deux fois et les deux entités sont les propriétaires de la relation. Cela pourrait-il causer des problèmes d'hibernation? Quel type de problèmes?

Merci!

le code ci-dessus est d'un autre post lié.

21
répondu jelies 2017-05-23 12:10:33

comme alternative aux solutions JPA / Hibernate : vous pouvez utiliser une clause de suppression en CASCADE dans la définition de base de données de votre clé foregin sur votre table de jointure, telle que (syntaxe Oracle) :

CONSTRAINT fk_to_group
     FOREIGN KEY (group_id)
     REFERENCES group (id)
     ON DELETE CASCADE

de cette façon le SGBD lui-même supprime automatiquement la ligne qui pointe vers le groupe lorsque vous supprimez le groupe. Et cela fonctionne que la suppression soit faite à partir de Hibernate/JPA, JDBC, manuellement dans le DB ou de toute autre manière.

la caractéristique de suppression en cascade est supporté par tous les principaux SGBD (Oracle, MySQL, SQL Server, PostgreSQL).

6
répondu Pierre Henry 2015-06-29 09:50:11

pour ce que ça vaut, J'utilise EclipseLink 2.3.2.v20111125-r10461 et si j'ai une relation unidirectionnelle avec @ManyToMany, j'observe le problème que vous décrivez. Cependant, si je le change pour être une relation bi-directionnelle @ManyToMany je suis en mesure de supprimer une entité du côté non-propriétaire et la table de jointure est mis à jour de manière appropriée. Tout cela sans l'utilisation d'attributs en cascade.

3
répondu NBW 2012-06-02 03:21:27

C'est une bonne solution. La meilleure partie est sur le côté SQL - peaufiner à n'importe quel niveau est facile.

j'ai utilisé MySql et MySql Workbench pour la Cascade sur Supprimer pour la clé étrangère requise.

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
1
répondu user1776955 2017-07-09 02:18:54

ça marche pour moi:

@Transactional
public void remove(Integer groupId) {
    Group group = groupRepository.findOne(groupId);
    group.getUsers().removeAll(group.getUsers());

    // Other business logic

    groupRepository.delete(group);
}

aussi, marquez la méthode @Transactional (org.springframework.transaction.annotation.Transactionnel), cela fera tout le processus en une seule session, économise du temps.

0
répondu Mehul Katpara 2018-03-06 21:16:23