Hibernate多对多关联:左侧收集包含元素,但右侧收集是空的

时间:2011-08-30 19:25:17

标签: java hibernate many-to-many hibernate-mapping

我的持久层中存在多对多关联的问题。我的方案如下:

用户可以拥有多个角色,角色可以附加多个用户。在测试期间,我遇到了一个奇怪的行为。我创建了角色对象和几个用户对象。角色已设置给每个用户。在此之后,使用DAO保存用户。然后加载一个用户以检查他是否在保存用户对象之前获得了传递给他的角色。在用户上调用getRoles()表示角色设置正确。

要检查反向是否也有效,角色对象将使用角色DAO从数据库加载。但是在角色对象上调用getUsers()只会返回一个空集,尽管它应该包含具有此角色的所有用户。

我仔细检查了数据库表,但一切似乎都没问题。用户,角色和user_role表都已正确填充。

那么为什么角色对象不包含任何用户?

我正在使用Hibernate和Spring以下类。

用户类

@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>();      

    ... 
}

角色等级

@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>();          

    ... 
}

测试我在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("user1@user.de");        
    user1.getRoles().add(userAdminRole);
    user1.getRoles().add(userRole);
    userDao.save(user1);

    User user2 = new User();
    user2.setEmail("user2@user.de");
    user2.getRoles().add(role);
    userDao.save(user2);

    User user3 = new User();
    user3.setEmail("user3@user.de");
    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());
}  

更新

抱歉,我忘了提一件事。当我测试代码时,我当然没有在每个类文件中标记多次关联。相反,我在每个类文件中使用选项1或选项2。

2 个答案:

答案 0 :(得分:9)

问题可能来自于您两次映射相同的双向关联。如果你告诉Hibernate两次关于同一个连接表或连接列,就会出现问题。在双向关联中,关联的一端必须映射关联,而另一端必须使用mappedBy属性告诉Hibernate它是另一端的倒数。

由于多对多是完全对称的,因此选择一个结尾作为所有者(即映射关联的结尾,因此具有@JoinTable注释)。另一方面是反向的,因此没有@JoinTable注释,但具有mappedBy属性。

示例:

@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>();          

    ... 
}

附加说明:

  • targetEntity没用,因为Hibernate知道它归功于Set的泛型类型。如果Set为Set<SomeInterface>
  • ,那将非常有用
  • CascadeType.ALL肯定不是你想要的。删除用户时是否要删除用户的所有角色?具有这些角色的其他用户应该怎么做?
  • 您几乎应该始终初始化双向关联的两端。 Hibernate考虑了所有者端(即没有mappedBy属性的结尾)来保持关联。
  • 所有这些都在Hibernate reference documentation中解释。阅读它:它充满了有用的信息,并不难理解。

答案 1 :(得分:2)

在处理双向多对多关联时,您必须维护关联的两端。在您的情况下,您还必须将用户添加到角色。将角色添加到用户不足以建立双向关联,您可以阅读书籍Java Persistance with Hibernate

  

与往常一样,双向关联(无论多重性)要求您设置关联的两端。