小编典典

Hibernate多对多关联:左侧集合包含元素,但右侧集合为空

hibernate

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

一个用户可以具有多个角色,并且一个角色可以具有与其连接的多个用户。在测试期间,我遇到了一个奇怪的行为。我创建了角色对象和几个用户对象。该角色已设置给每个用户。此后,使用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。


阅读 279

收藏
2020-06-20

共1个答案

小编典典

问题可能是由于您两次映射了相同的双向关联。如果您对相同的联接表或联接列两次告诉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参考文档中进行了说明。阅读:它充满了有用的信息,并不难理解。
2020-06-20