java - inversejoincolumns - ¿Cómo eliminar la entidad con la relación ManyToMany en JPA(y las correspondientes filas de la tabla de unión)?
spring boot jpa hibernate many to many example (7)
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 las filas de la tabla de unión y la operación de eliminación falla debido a restricciones de clave externa. Llamar a eliminar () en Usuario funciona bien (supongo que esto tiene algo que ver con ser dueño de la relación).
Entonces, ¿cómo puedo eliminar un grupo en este caso?
La única forma en que podría llegar es cargar todos los usuarios en el grupo, luego, para cada usuario, eliminar el grupo actual de sus grupos y actualizar al usuario. Pero me parece ridículo llamar a update () sobre cada usuario del grupo solo para poder eliminar este grupo.
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 unión, como (sintaxis Oracle):
CONSTRAINT fk_to_group
FOREIGN KEY (group_id)
REFERENCES group (id)
ON DELETE CASCADE
De esta forma, el DBMS mismo elimina automáticamente la fila que apunta al grupo cuando elimina el grupo. Y funciona tanto 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).
El siguiente funciona para mi. Agregue el siguiente método a la entidad que no es el propietario 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 que no se hace automáticamente). por lo que cada vez que agregue 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 del grupo.
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;
}
Lo intenté y funciona. Cuando elimina Rol, también se eliminan las relaciones (pero no las entidades de Permiso) y cuando elimina el Permiso, las relaciones con Rol también se eliminan (pero no la instancia de Rol). Pero estamos mapeando una relación unidireccional dos veces y ambas entidades son las propietarias de la relación. ¿Podría esto causarle algunos problemas a Hibernate? ¿Qué tipo de problemas?
¡Gracias!
El código de arriba es de otra post relacionada.
Esta es una buena solución. La mejor parte está en el lado de SQL: ajustar fácilmente a cualquier nivel es fácil.
Utilicé MySql y MySql Workbench para Cascade en eliminar para la clave externa requerida.
ALTER TABLE schema.joined_table
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
Esto funciona para mí:
@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á que todo el proceso en una sola sesión, ahorre tiempo.
Por lo que vale, estoy usando EclipseLink 2.3.2.v20111125-r10461 y si tengo una relación unidireccional @ManyToMany observo el problema que describes. Sin embargo, si lo cambio para que sea una relación @ManyToMany bidireccional, puedo eliminar una entidad del lado no propietario y la tabla JOIN se actualiza de manera apropiada. Esto es todo sin el uso de ningún atributo en cascada.
- La propiedad de la relación está determinada por dónde coloca el atributo ''mappedBy'' en la anotación. La entidad que colocas ''mappedBy'' es la que NO es el propietario. No hay posibilidad de que ambos lados sean dueños. Si no tiene un caso de uso de "eliminar usuario", simplemente puede mover la propiedad a la entidad de
Group
, ya que actualmente elUser
es el propietario. - Por otro lado, no has preguntado al respecto, pero hay algo que vale la pena saber. Los
groups
yusers
no se combinan entre sí. Quiero decir, después de eliminar la instancia de User1 de Group1.users, las colecciones de User1.groups no se cambian automáticamente (lo cual es bastante sorprendente para mí), - En general, le sugiero que decida quién es el propietario. Digamos que el
User
es el propietario. Luego, al eliminar un usuario, la relación usuario-grupo se actualizará automáticamente. Pero al eliminar un grupo, tienes que encargarte de borrar la relación tú mismo así:
entityManager.remove(group)
for (User user : group.users) {
user.groups.remove(group);
}
...
// then merge() and flush()