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 de nombreux 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 l'opération de suppression échoue en raison de contraintes de clé étrangère. L'appel de remove () sur User fonctionne 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 est de charger tous les utilisateurs du groupe, puis pour chaque utilisateur, supprimez le groupe actuel de ses groupes et mettez à jour l'utilisateur. Mais il me semble ridicule d'appeler update () sur chaque utilisateur du groupe juste pour pouvoir supprimer ce groupe.
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 disposer d'une liste à 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 ajouter un utilisateur à la liste d'utilisateurs dans l'entité Groupe.
la source
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 d'autorisation) et lorsque vous supprimez l'autorisation, 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 le propriétaire de la relation. Cela pourrait-il causer des problèmes de mise en veille prolongée? Quel type de problèmes?
Merci!
Le code ci-dessus provient d'un autre article lié.
la source
Comme alternative aux solutions JPA / Hibernate: vous pouvez utiliser une clause CASCADE DELETE 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 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 base de données ou de toute autre manière.
la fonction de suppression en cascade est prise en charge par tous les principaux SGBD (Oracle, MySQL, SQL Server, PostgreSQL).
la source
Pour ce que ça 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 le change en une relation bidirectionnelle @ManyToMany, je suis en mesure de supprimer une entité du côté non propriétaire et la table JOIN est mise à jour de manière appropriée. Tout cela sans l'utilisation d'attributs en cascade.
la source
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); }
Aussi, marquez la méthode @Transactional (org.springframework.transaction.annotation.Transactional), cela fera tout le processus en une seule session, ce qui vous fera gagner du temps.
la source
C'est une bonne solution. La meilleure partie est du côté SQL - il est facile de régler finement à n'importe quel niveau.
J'ai utilisé MySql et MySql Workbench pour Cascade lors de la suppression de la clé étrangère requise.
ALTER TABLE schema.joined_table ADD CONSTRAINT UniqueKey FOREIGN KEY (key2) REFERENCES schema.table1 (id) ON DELETE CASCADE;
la source
C'est ce que j'ai fini par faire. J'espère que quelqu'un pourra le trouver utile.
@Transactional public void deleteGroup(Long groupId) { Group group = groupRepository.findById(groupId).orElseThrow(); group.getUsers().forEach(u -> u.getGroups().remove(group)); userRepository.saveAll(group.getUsers()); groupRepository.delete(group); }
la source
Pour mon cas, j'ai supprimé les tables mappedBy et jointes comme ceci:
@ManyToMany(cascade = CascadeType.ALL) @JoinTable(name = "user_group", joinColumns = { @JoinColumn(name = "user", referencedColumnName = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "group", referencedColumnName = "group_id") }) private List<User> users; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JsonIgnore private List<Group> groups;
la source
Cela fonctionne pour moi sur un problème similaire où je n'ai pas réussi à supprimer l'utilisateur en raison de la référence. Merci
@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
la source