2009-07-04 13 views
65

Digamos que tengo dos entidades: Grupo y Usuario. Cada usuario puede ser miembro de muchos grupos y cada grupo puede tener muchos usuarios.¿Cómo eliminar la entidad con la relación ManyToMany en JPA (y las correspondientes filas de la tabla de unión)?

@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 EntityManager.remove() en algún proveedor de grupo, JPA (en mi caso Hibernate) no elimina las filas de tabla de unión y eliminar la operació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 pensar es en cargar a 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() sobre cada usuario del grupo solo para poder eliminar este grupo.

Respuesta

55
  • La propiedad de la relación está determinada por el lugar donde 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 Group, ya que actualmente el propietario es el User.
  • Por otro lado, no ha preguntado al respecto, pero hay algo que vale la pena saber. groups y users no se combinan entre sí. Quiero decir, después de eliminar ejemplo Usuario1 de Group1.users, las colecciones User1.groups no se cambia automáticamente (lo cual es bastante sorprendente para mí),
  • Con todo, sugeriría a decidir 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. Sin embargo, cuando se elimina un grupo que tiene que hacerse cargo de la supresión de la relación a sí mismo de esta manera:

entityManager.remove(group) 
for (User user : group.users) { 
    user.groups.remove(group); 
} 
... 
// then merge() and flush() 
+0

Thnx! Tuve el mismo problema y tu solución lo resolvió. Pero debo saber si hay otra forma de resolver este problema. Produce un código horrible. ¿Por qué no puede ser _em.remove (entidad) _ y eso es todo? –

+1

¿Está optimizado detrás de escena? porque no quiero consultar todo el conjunto de datos. – Ced

+0

Este es un enfoque realmente malo, ¿qué pasa si tienes varios miles de usuarios en ese grupo? – Serg

20

He encontrado 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", 
      [email protected](name="Role_id"), 
      [email protected](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", 
      [email protected](name="Permission_id"), 
      [email protected](name="Role_id") 
     ) 
    public List<Role> getRoles() { 
     return roles; 
    } 

    public void setRoles(List<Role> roles) { 
     this.roles = roles; 
    } 

He intentado esto 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 anterior es de otro post relacionados.

+0

problemas refrescantes de la colección? – jelies

+0

¿Por qué esta respuesta no se actualiza como respuesta aprobada? – kinkajou

+9

Esto no es correcto. Bidireccional ManyToMany debe tener uno y solo un lado del propietario de la relación. Esta solución está convirtiendo a ambas partes en propietarias y eventualmente dará como resultado registros duplicados. Para evitar registros duplicados, se deben usar Conjuntos en lugar de Listas. Pero, usar Set es solo una solución para hacer algo que no se recomienda. –

3

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 @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.

31

Lo siguiente me funciona.Agregue el método siguiente 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 los usuarios (que no se realiza de forma automática) . 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.

+0

cascade = CascadeType.ALL no se debe configurar cuando desee utilizar esta solución. De lo contrario, funciona perfecto! – ltsstar

6

Como alternativa a las soluciones JPA/Hibernate: se puede utilizar un CASCADE DELETE cláusula en la definición de base de datos de su clave foregin en su tabla de unión, tales como (sintaxis de 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).

1

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; 
+0

Bienvenido a SO. Tómese un momento y estudie esto para mejorar su formateo y corrección: https://stackoverflow.com/help/how-to-ask – petezurich

0

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, el método de marcar @Transactional (org.springframework.transaction.annotation.Transactional), esto va a hacer todo el proceso en una sola sesión, ahorra algo de tiempo .

Cuestiones relacionadas