2011-08-30 25 views
7

Tengo un problema con una asociación muchos a muchos en mi capa de persistencia. Mi situación es la siguiente:Hibernate Asociación muchos a muchos: la colección del lado izquierdo contiene elementos, pero la colección del lado derecho está vacía

Un usuario puede tener varios roles y un rol puede tener varios usuarios conectados. Durante las pruebas, encontré un comportamiento extraño. Creé un objeto de rol y varios objetos de usuario. El rol se estableció para cada uno de los usuarios. Después de esto, los usuarios se salvaron usando un DAO. Luego uno de los usuarios se carga para verificar si obtuvo el rol que se le pasó antes de guardar el objeto del usuario. Llamar al getRoles() en el usuario muestra que el rol se configuró correctamente.

Para comprobar si la dirección inversa también funciona, el objeto de rol se carga de la base de datos utilizando una función DAO. Pero al llamar al getUsers() en el objeto de la función simplemente devuelve un conjunto vacío, aunque debe contener a todos los usuarios con esta función.

Comprobé dos veces la tabla de la base de datos, pero todo parece estar bien. El usuario, la función y la tabla user_role se completaron correctamente.

¿Por qué el objeto de rol no contiene ningún usuario?

Estoy usando Hibernate y Spring con las siguientes clases.

clase Usuario

@Entity 
@Table 
public class User extends BusinessObject { 

    ... 

    // Option 1 
    @ManyToMany(fetch = FetchType.LAZY, 
       cascade = CascadeType.ALL, 
       targetEntity=Role.class) 
    @JoinTable(name= "user_role", 
       joinColumns = {@JoinColumn(name="user_id")}, 
       inverseJoinColumns = {@JoinColumn(name="role_id")}) 

    // Option 2 
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    @JoinTable(name= "user_role", 
        joinColumns = {@JoinColumn(name="user_id")}, 
      inverseJoinColumns = {@JoinColumn(name="role_id")}) 
    private Set<Role> roles = new HashSet<Role>();  

    ... 
} 

clase Rol

@Entity 
@Table 
public class Role extends BusinessObject { 
    ... 

    // Option 1 
    @ManyToMany(fetch = FetchType.LAZY, 
       cascade = CascadeType.ALL, 
       mappedBy= "roles", 
       targetEntity = User.class) 

    // Option 2 
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    @JoinTable(name= "user_role", 
        joinColumns = {@JoinColumn(name="role_id")}, 
        inverseJoinColumns = {@JoinColumn(name="user_id")}) 
    private Set<User> users = new HashSet<User>();   

    ... 
} 

Para probar que estoy usando el siguiente código en una clase de prueba JUnit.

@Test 
public void test(){  
    Transaction trans = sessionFactory.getCurrentSession().beginTransaction(); 

    Role userAdminRole = new Role(); 
    userAdminRole.setName(RoleName.USER_ADMIN); 
    Role userRole = new Role(); 
    userRole.setName(RoleName.USER); 

    User user1 = new User(); 
    user1.setEmail("[email protected]");   
    user1.getRoles().add(userAdminRole); 
    user1.getRoles().add(userRole); 
    userDao.save(user1); 

    User user2 = new User(); 
    user2.setEmail("[email protected]"); 
    user2.getRoles().add(role); 
    userDao.save(user2); 

    User user3 = new User(); 
    user3.setEmail("[email protected]"); 
    user3.getRoles().add(role); 
    userDao.save(user3);    

    trans.commit();  

    User loadedUser = userDao.load(user1.getId()); 

      // Tests passes 
    Assert.assertNotNull(loadedUser); 
    Assert.assertEquals(user1, loadedUser); 

    Set<Role> roles = loadedUser.getRoles();   

      // Tests passes 
    Assert.assertEquals(2, roles.size()); 

    Role loadedUserAdminRole = roleDao.findByName(RoleName.USER_ADMIN); 
    Set<User> users = loadedUserAdminRole.getUsers(); 

    // Test fails: Count is 0 instead of 3 !!!!!!! 
    Assert.assertEquals(3, users.size()); 
} 

ACTUALIZACIÓN

Lo siento, se olvidó de mencionar una cosa. Cuando probé el código, por supuesto, no marqué la asociación de muchos a muchos dos veces en cada archivo de clase. En cambio, utilicé la opción 1 o la opción 2 en cada archivo de clase.

+0

¿Dónde está iniciando y terminando sesiones (o 'EntityManager's en JPA)? Sospecho que si cierras una sesión después del 'save's y comienzas una nueva antes de' load's, las cosas funcionarán, porque extraerá los objetos de la base de datos en vez de la caché de la sesión. –

+0

La sesión se abre durante todo el método de prueba. Llamar a getSession() en uno de los métodos de DAO devuelve siempre el mismo objeto de sesión siempre que se invoquen en un método de prueba. – Flo

Respuesta

9

El problema probablemente proviene del hecho de que asignó la misma asociación bidireccional dos veces. Si le dices a Hibernate dos veces acerca de la misma tabla de combinación o columna de unión, hay un problema. En una asociación bidireccional, uno de los extremos de la asociación debe mapear la asociación, y el otro debe decirle a Hibernate que es el inverso del otro extremo, usando el atributo mappedBy.

Dado que muchos a muchos son completamente simétricos, elija uno de los extremos para ser el propietario (es decir, el extremo que mapea la asociación, y así tener la anotación @JoinTable).El otro lado es simplemente el inverso, y por lo tanto no tiene una anotación @JoinTable, pero tiene un atributo mappedBy.

Ejemplo:

@Entity 
@Table 
public class User extends BusinessObject { 

    ... 

    // This end is the owner of the association 
    @ManyToMany 
    @JoinTable(name= "user_role", 
       joinColumns = {@JoinColumn(name="user_id")}, 
       inverseJoinColumns = {@JoinColumn(name="role_id")}) 
    private Set<Role> roles = new HashSet<Role>();  
    ... 
} 

@Entity 
@Table 
public class Role extends BusinessObject { 
    ... 

    // This end is not the owner. It's the inverse of the User.roles association 
    @ManyToMany(mappedBy = "roles") 
    private Set<User> users = new HashSet<User>();   

    ... 
} 

Notas adicionales:

  • targetEntity no es útil, ya que Hibernate sabe que gracias al tipo genérico de la Set. Sería útil si el Conjunto fuera Set<SomeInterface>
  • CascadeType.ALL ciertamente no es lo que desea. ¿Desea eliminar todas las funciones de un usuario al eliminar un usuario? ¿Qué debería pasar con los otros usuarios que tienen estos roles?
  • Casi siempre debe inicializar ambos extremos de una asociación bidireccional. Hibernate tiene en cuenta el extremo del propietario (es decir, el extremo sin el atributo mappedBy) para conservar la asociación.
  • Todo esto se explica en el Hibernate reference documentation. Léelo: está lleno de información útil y no es difícil de entender.
+0

Cambió la asociación de la forma que aconsejó sin ninguna declaración en cascada y también establece la asociación en ambos extremos. Ahora funciona como se esperaba. – Flo

2

Cuando se trata de una asociación bidireccional de muchos a muchos, debe mantener ambos extremos de la asociación. En su caso, también debe agregar al usuario al rol. La adición de la función al usuario no es suficiente para establecer una asociación bidireccional como se puede leer en el libro Java Persistance with Hibernate:

Como siempre, una asociación bidireccional (no importa de qué multiplicidad) requiere que establezca ambos extremos de la asociación.

Cuestiones relacionadas