Digamos que eu tenha duas entidades: Grupo e Usuário. Cada usuário pode ser membro de muitos grupos e cada grupo pode ter muitos usuários.
@Entity
public class User {
@ManyToMany
Set<Group> groups;
//...
}
@Entity
public class Group {
@ManyToMany(mappedBy="groups")
Set<User> users;
//...
}
Agora, quero remover um grupo (digamos que ele tenha muitos membros).
O problema é que quando eu chamo EntityManager.remove () em algum Grupo, o provedor JPA (no meu caso Hibernate) não remove linhas da tabela de junção e a operação de exclusão falha devido a restrições de chave estrangeira. Chamar remove () em User funciona bem (acho que isso tem algo a ver com o lado proprietário do relacionamento).
Então, como posso remover um grupo neste caso?
A única maneira que eu poderia inventar é carregar todos os usuários no grupo, então para cada usuário remover o grupo atual de seus grupos e atualizar o usuário. Mas me parece ridículo chamar update () em cada usuário do grupo apenas para poder deletar este grupo.
O seguinte funciona para mim. Adicione o seguinte método à entidade que não é a proprietária do relacionamento (Grupo)
@PreRemove private void removeGroupsFromUsers() { for (User u : users) { u.getGroups().remove(this); } }
Lembre-se que para que isso funcione, o Grupo deve ter uma lista de Usuários atualizada (o que não é feito automaticamente). portanto, toda vez que você adiciona um Grupo à lista de grupos na entidade Usuário, também deve adicionar um Usuário à lista de usuários na entidade Grupo.
fonte
Encontrei uma solução possível, mas ... não sei se é uma boa solução.
@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; }
Eu tentei isso e funciona. Quando você exclui Role, também as relações são excluídas (mas não as entidades Permission) e quando você exclui Permission, as relações com Role são excluídas também (mas não a instância Role). Mas estamos mapeando uma relação unidirecional duas vezes e ambas as entidades são as proprietárias da relação. Isso poderia causar alguns problemas para o Hibernate? Que tipo de problemas?
Obrigado!
O código acima é de outro post relacionado.
fonte
Como alternativa às soluções JPA / Hibernate: você pode usar uma cláusula CASCADE DELETE na definição do banco de dados de sua chave anterior em sua tabela de junção, como (sintaxe Oracle):
CONSTRAINT fk_to_group FOREIGN KEY (group_id) REFERENCES group (id) ON DELETE CASCADE
Dessa forma, o próprio DBMS exclui automaticamente a linha que aponta para o grupo quando você exclui o grupo. E funciona seja a exclusão feita do Hibernate / JPA, JDBC, manualmente no BD ou de qualquer outra forma.
o recurso de exclusão em cascata é compatível com todos os SGBD principais (Oracle, MySQL, SQL Server, PostgreSQL).
fonte
Para constar, estou usando EclipseLink 2.3.2.v20111125-r10461 e se eu tiver um relacionamento unidirecional @ManyToMany, observo o problema que você descreve. No entanto, se eu alterar para um relacionamento @ManyToMany bidirecional, poderei excluir uma entidade do lado que não é proprietário e a tabela JOIN será atualizada de forma adequada. Isso tudo sem o uso de atributos em cascata.
fonte
Isso funciona para mim:
@Transactional public void remove(Integer groupId) { Group group = groupRepository.findOne(groupId); group.getUsers().removeAll(group.getUsers()); // Other business logic groupRepository.delete(group); }
Além disso, marque o método @Transactional (org.springframework.transaction.annotation.Transactional), isso fará todo o processo em uma sessão, economiza tempo.
fonte
Esta é uma boa solução. A melhor parte está no lado do SQL - o ajuste fino em qualquer nível é fácil.
Usei MySql e MySql Workbench para cascatear na exclusão da chave estrangeira necessária.
ALTER TABLE schema.joined_table ADD CONSTRAINT UniqueKey FOREIGN KEY (key2) REFERENCES schema.table1 (id) ON DELETE CASCADE;
fonte
No meu caso, excluí mappedBy e juntei tabelas como esta:
@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;
fonte
Isso funciona para mim em um problema semelhante, em que não consegui excluir o usuário devido à referência. Obrigado
@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
fonte
Isso é o que acabei fazendo. Esperançosamente, alguém pode achar isso útil.
@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); }
fonte
deleteGroup
irá acionar 1 (selecionar) + 100 (excluir) + 100 (atualizar) + 1 (excluir) consultas.