Disons que j'ai deux entités: groupe et utilisateur. Chaque utilisateur peut être membre de nombreux 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, lorsque j'appelle EntityManager.remove () sur un groupe, le fournisseur JPA (dans mon cas, Hibernate) ne supprime pas les lignes de la table jointe et que 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?
La seule façon dont je pourrais arriver est de 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 simplement pour pouvoir supprimer ce groupe.
Group
, car actuellement User
est le propriétaire.groups
et users
ne sont pas combinés. 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)User
est le propriétaire. Ensuite, lors de la suppression d'un utilisateur, la relation groupe d'utilisateurs sera automatiquement mise à jour. Mais lors de la suppression d'un groupe, vous devez vous assurer de supprimer vous-même 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);
}
}
N'oubliez pas que pour que cela fonctionne, le groupe doit disposer d'une liste d'utilisateurs mise à jour (ce qui n'est pas fait automatiquement). Par conséquent, 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 de 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é cela et ça marche. 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 mappons une relation unidirectionnelle deux fois et les deux entités sont le propriétaire de la relation. Cela pourrait-il causer des problèmes à Hibernate? Quel type de problèmes?
Merci!
Le code ci-dessus provient d'un autre post related.
Comme alternative aux solutions JPA/Hibernate: vous pouvez utiliser une clause CASCADE DELETE dans la définition de la 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
Ainsi, 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 effectuée à 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).
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. Toutefois, si je change la relation en une relation bidirectionnelle @ManyToMany, je peux supprimer une entité du côté des non-propriétaires et la table JOIN est mise à jour de manière appropriée. Tout cela sans l'utilisation d'attributs en cascade.
C'est une bonne solution. La meilleure partie est du côté SQL: il est facile d’ajuster tous les niveaux.
J'ai utilisé MySql et MySql Workbench pour mettre en cascade en suppression la clé étrangère requise.
ALTER TABLE schema.joined_table
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
Pour mon cas, j'ai supprimé les tables mappées 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;
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), cela fera tout le processus en une session et vous fera gagner du temps.