简体   繁体   English

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

[英]Hibernate Many-to-many association: left hand side collection contains elements, but right hand side collection is empty

I got a problem with a many to many association in my persistence layer. 我的持久层中出现了多对多关联的问题。 My scenario is the following: 我的方案如下:

A user can has several roles and a role can have several user attached to it. 用户可以拥有多个角色,角色可以拥有多个用户。 During the tests I encountered a strange behavior. 在测试期间,我遇到了一个奇怪的行为。 I created role object and several user objects. 我创建了角色对象和几个用户对象。 The role was set to each of the users. 角色已设置给每个用户。 After this the users were saved using a DAO. 在此之后,使用DAO保存用户。 Then one of the user gets loaded to check whether he got the role that was passed to him before saving the user object. 然后加载一个用户以检查他是否在保存用户对象之前获得了传递给他的角色。 Calling getRoles() on the user shows that the role was set correctly. 在用户上调用getRoles()表明角色设置正确。

To check whether the inverse direction also works the role object gets loaded from the database using a role DAO. 要检查反向是否也有效,角色对象将使用角色DAO从数据库加载。 But calling getUsers() on the role object just returns an empty set, although it should contain all the users with this role. 但是在角色对象上调用getUsers()只会返回一个空集,尽管它应该包含具有此角色的所有用户。

I double checked the database table but everything seems all right. 我仔细检查了数据库表,但一切似乎都没问题。 User, role and user_role table were all filled correctly. 用户,角色和user_role表都已正确填充。

So why doesn't the role object contain any user? 那么为什么角色对象不包含任何用户呢?

I'm using Hibernate and Spring with the following classes. 我正在使用Hibernate和Spring以下类。

User class 用户类

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

    ... 
}

Role class 角色类

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

    ... 
}

To test I'm using the following code in a JUnit test class. 测试我在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());
}  

UPDATE UPDATE

Sorry I forgot to mention one thing. 对不起,我忘了提一件事。 When I tested the code I of course didn't mark the many to many association twice in each class file. 当我测试代码时,我当然没有在每个类文件中标记多次关联。 Instead I used either option 1 or option 2 in each class file. 相反,我在每个类文件中使用选项1或选项2。

The problem probably comes from the fact that you mapped the same bidirectional association twice. 问题可能来自于您两次映射相同的双向关联。 If you tell Hibernate twice about the same join table or join column, there is a problem. 如果你告诉Hibernate两次关于同一个连接表或连接列,就会出现问题。 In a bidirectional association, one of the ends of the association must map the association, and the other one must tell Hibernate that it's the inverse of the other end, using the mappedBy attribute. 在双向关联中,关联的一端必须映射关联,另一端必须使用mappedBy属性告诉Hibernate它是另一端的反转。

Since a many-to-many is completely symmetric, choose one of the end to be the owner (ie the end which maps the association, and thus have the @JoinTable annotation). 由于多对多是完全对称的,因此选择一个结尾作为所有者(即映射关联的结尾,因此具有@JoinTable注释)。 The other side is just the inverse, and thus doesn't have a @JoinTable annotation, but has a mappedBy attribute. 另一方面是反向的,因此没有@JoinTable注释,但有一个mappedBy属性。

Example: 例:

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

    ... 
}

Additional notes: 补充说明:

  • targetEntity isn't useful, since Hibernate knows it thanks to the generic type of the Set . targetEntity是没有用的,因为Hibernate知道这多亏了泛型类型的Set It would be useful if the Set was a Set<SomeInterface> 如果Set是Set<SomeInterface>那将会很有用
  • CascadeType.ALL is certainly not what you want. CascadeType.ALL肯定不是你想要的。 Do you want to delete all the roles of a user when deleting a user? 删除用户时是否要删除用户的所有角色? What should happen to the other users having these roles? 具有这些角色的其他用户应该怎么做?
  • You should almost always initialize both ends of a bidirectional association. 您几乎应该始终初始化双向关联的两端。 Hibernate takes into account the owner end (ie the end without the mappedBy attribute) to persist the association. Hibernate考虑所有者端(即没有mappedBy属性的结尾)来持久化关联。
  • All of this is explained in the Hibernate reference documentation . 所有这些都在Hibernate参考文档中进行了解释。 Read it: it's full of useful information, and is not hard to understand. 阅读它:它充满了有用的信息,并不难理解。

When dealing with a bidirectional many-to-many association you have to maintain both ends of the association. 在处理双向多对多关联时,您必须维护关联的两端。 In your case, you have to add the user to the role as well. 在您的情况下,您还必须将用户添加到角色。 Adding the role to the user isn't sufficient to establish a bidirectional association as you can read in book Java Persistance with Hibernate : 将角色添加到用户不足以建立双向关联,您可以在书籍Java Persistance with Hibernate中阅读

As always, a bidirectional association (no matter of what multiplicity) requires that you set both ends of the association. 与往常一样,双向关联(无论多重性)要求您设置关联的两端。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM