Digamos que tengo dos entidades: Grupo y Usuario. Cada usuario puede ser miembro de muchos grupos y cada grupo puede tener muchos usuarios.
@Entity
public class User {
@ManyToMany
Set<Group> groups;
//...
}
@Entity
public class Group {
@ManyToMany(mappedBy="groups")
Set<User> users;
//...
}
Ahora quiero eliminar un grupo (digamos que tiene muchos miembros).
El problema es que cuando llamo a EntityManager.remove () en algún grupo, el proveedor de JPA (en mi caso, Hibernate) no elimina filas de la tabla de unión y la operación de eliminación falla debido a restricciones de clave externa. Llamar a remove () en User funciona bien (supongo que esto tiene algo que ver con el lado propietario de la relación).
Entonces, ¿cómo puedo eliminar un grupo en este caso?
La única forma que se me ocurre es cargar todos los usuarios del grupo, luego, para cada usuario, eliminar el grupo actual de sus grupos y actualizar al usuario. Pero me parece ridículo llamar a update () en cada usuario del grupo solo para poder eliminar este grupo.
Lo siguiente funciona para mí. Agregue el siguiente método a la entidad que no es propietaria de la relación (Grupo)
@PreRemove private void removeGroupsFromUsers() { for (User u : users) { u.getGroups().remove(this); } }
Tenga en cuenta que para que esto funcione, el Grupo debe tener una lista actualizada de Usuarios (lo cual no se hace automáticamente). por lo que cada vez que agrega un Grupo a la lista de grupos en la entidad Usuario, también debe agregar un Usuario a la lista de usuarios en la entidad Grupo.
fuente
Encontré una posible solución, pero ... no sé si es una buena solución.
@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; }
He probado esto y funciona. Cuando elimina el rol, también se eliminan las relaciones (pero no las entidades de permiso) y cuando elimina el permiso, las relaciones con el rol también se eliminan (pero no la instancia del rol). Pero estamos mapeando una relación unidireccional dos veces y ambas entidades son las dueñas de la relación. ¿Podría esto causar algunos problemas para hibernar? ¿Qué tipo de problemas?
¡Gracias!
El código anterior es de otra publicación relacionada.
fuente
Como alternativa a las soluciones JPA / Hibernate: puede usar una cláusula CASCADE DELETE en la definición de la base de datos de su clave foregin en su tabla de combinación, como (sintaxis de Oracle):
CONSTRAINT fk_to_group FOREIGN KEY (group_id) REFERENCES group (id) ON DELETE CASCADE
De esa forma, el DBMS borra automáticamente la fila que apunta al grupo cuando lo borras. Y funciona si la eliminación se realiza desde Hibernate / JPA, JDBC, manualmente en la base de datos o de cualquier otra manera.
la función de eliminación en cascada es compatible con todos los principales DBMS (Oracle, MySQL, SQL Server, PostgreSQL).
fuente
Por lo que vale, estoy usando EclipseLink 2.3.2.v20111125-r10461 y si tengo una relación unidireccional @ManyToMany observo el problema que usted describe. Sin embargo, si lo cambio para que sea una relación bidireccional @ManyToMany, puedo eliminar una entidad del lado no propietario y la tabla JOIN se actualiza adecuadamente. Todo esto sin el uso de atributos en cascada.
fuente
Esto funciona para mi:
@Transactional public void remove(Integer groupId) { Group group = groupRepository.findOne(groupId); group.getUsers().removeAll(group.getUsers()); // Other business logic groupRepository.delete(group); }
Además, marque el método @Transactional (org.springframework.transaction.annotation.Transactional), esto hará todo el proceso en una sesión, ahorra algo de tiempo.
fuente
Esta es una buena solución. La mejor parte está en el lado de SQL: el ajuste fino a cualquier nivel es fácil.
Usé MySql y MySql Workbench en Cascade en la eliminación de la CLAVE externa requerida.
ALTER TABLE schema.joined_table ADD CONSTRAINT UniqueKey FOREIGN KEY (key2) REFERENCES schema.table1 (id) ON DELETE CASCADE;
fuente
Esto es lo que terminé haciendo. Ojalá alguien lo encuentre ú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); }
fuente
Para mi caso, eliminé el mappedBy y uní tablas 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;
fuente
Esto me funciona en un problema similar en el que no pude eliminar al usuario debido a la referencia. Gracias
@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
fuente