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.
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 leUser
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
etusers
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()
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.
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é.
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).
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.
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;
ç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.