Comment supprimer l’entité avec la relation ManyToMany dans JPA (et les lignes correspondantes de la jointure)?

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 groups; //... } @Entity public class Group { @ManyToMany(mappedBy="groups") Set users; //... } 

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

Le problème est que lorsque j’appelle EntityManager.remove () sur un groupe, le fournisseur JPA (dans mon cas Hibernate) ne supprime pas les lignes de la table de jointure et la suppression de l’opération échoue en raison de contraintes de clé étrangère. L’appel de remove () sur User fonctionne très bien (je suppose que cela a quelque chose à voir avec le côté propriétaire de la relation).

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

Le seul moyen que je pourrais trouver consiste à charger tous les utilisateurs du groupe, puis, pour chaque utilisateur, à supprimer le groupe actuel de ses groupes et à mettre à jour l’utilisateur. Mais il me semble ridicule d’appeler update () sur chaque utilisateur du groupe pour pouvoir supprimer ce groupe.

  • La propriété de la relation est déterminée par l’endroit où vous placez l’atsortingbut ‘mappedBy’ dans l’annotation. L’entité que vous mettez ‘mappedBy’ est celle qui n’est PAS le propriétaire. Il n’y a aucune chance que les deux parties soient propriétaires. Si vous ne disposez pas d’un cas d’utilisation “Supprimer un utilisateur”, vous pouvez simplement transférer la propriété à l’entité Group , car l’ User est actuellement le propriétaire.
  • Par contre, vous n’avez pas posé la question, mais une chose mérite d’être connue. Les groups et les users ne sont pas combinés les uns avec les autres. Je veux dire, après avoir supprimé l’instance User1 de Group1.users, les collections User1.groups ne sont pas modifiées automatiquement (ce qui est assez surprenant pour moi),
  • Dans l’ensemble, je vous suggère de décider qui est le propriétaire. Laissez dire que l’ User est le propriétaire. Ensuite, lors de la suppression d’un utilisateur, le groupe d’utilisateurs de la relation sera automatiquement mis à jour. Mais lorsque vous supprimez un groupe, vous devez vous occuper de supprimer la relation comme ceci:

 entityManager.remove(group) for (User user : group.users) { user.groups.remove(group); } ... // then merge() and flush() 

Ce qui suit fonctionne 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 de groupes dans l’entité utilisateur, vous devez également append un utilisateur à la liste d’utilisateurs de l’entité de groupe.

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 getPermissions() { return permissions; } public void setPermissions(List 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 getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; } 

J’ai essayé ceci et ça marche. Lorsque vous supprimez Role, les relations sont également supprimées (mais pas les entités Permission) et lorsque vous supprimez Permission, les relations avec Role sont également supprimées (mais pas l’instance Role). Mais nous cartographions une relation unidirectionnelle deux fois et les deux entités sont le propriétaire de la relation. Est-ce que cela pourrait causer des problèmes à Hibernate? Quel type de problèmes?

Merci!

Le code ci-dessus provient d’un autre article lié.

En alternative aux solutions JPA / Hibernate: vous pouvez utiliser une clause CASCADE DELETE dans la définition de firebase database de votre clé d’ouverture, telle que (syntaxe Oracle):

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

De cette manière, 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 la firebase database ou de toute autre manière.

La fonctionnalité de suppression en cascade est prise en charge par tous les principaux SGBD (Oracle, MySQL, SQL Server, PostgreSQL).

Pour ce que cela vaut, j’utilise EclipseLink 2.3.2.v20111125-r10461 et si j’ai une relation unidirectionnelle @ManyToMany, j’observe le problème que vous décrivez. Cependant, si je modifie la relation en une relation bidirectionnelle @ManyToMany, je peux supprimer une entité du côté non propriétaire et la table JOIN est mise à jour de manière appropriée. Tout cela sans utiliser des atsortingbuts en cascade.

C’est une bonne solution. La meilleure partie est du côté SQL – le réglage fin à n’importe quel niveau est facile.

J’ai utilisé MySQL et MySQL Workbench en cascade sur la suppression de la clé étrangère requirejse.

 ALTER TABLE schema.joined_table ADD CONSTRAINT UniqueKey FOREIGN KEY (key2) REFERENCES schema.table1 (id) ON DELETE CASCADE; 

Cela fonctionne pour moi:

 @Transactional public void remove(Integer groupId) { Group group = groupRepository.findOne(groupId); group.getUsers().removeAll(group.getUsers()); // Other business logic groupRepository.delete(group); } 

En outre, marquez la méthode @Transactional (org.springframework.transaction.annotation.Transactional), ce qui permet d’effectuer tout le processus en une seule session et de gagner du temps.