Hibernate Lots to many: the left hand collection contains elements, but the right hand collection is empty

I am having a problem with many, many associations in my save level. My scenario is as follows:

A user can have multiple roles, and a role can have multiple users. During the tests, I came across strange behavior. I created a role object and several custom objects. A role has been set for each of the users. After that, users were saved using the DAO. Then one of the users logs in to check if he received the role that was transferred to him before saving the user object. A call to getRoles() for the user indicates that the role was installed correctly.

To check if the reverse direction is working, the role object is loaded from the database using the DAO role. But calling getUsers() on the role object simply returns an empty set, although it should contain all users with this role.

I double checked the database table, but everything seems to be correct. The user, role, and user_role table were populated correctly.

So why does the role object not contain a user?

I use Hibernate and Spring with the following classes.

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>(); ... } 

For testing, I use the following code in the JUnit test class.

 @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

Sorry, I forgot to mention one thing. When I tested the code, of course, I did not mark many, many associations twice in each class file. Instead, I used option 1 or option 2 in each class file.

+7
source share
2 answers

The problem is probably due to the fact that you have mapped the same bidirectional connection twice. If you tell Hibernate twice about the same join table or in a join column, a problem occurs. In bidirectional communication, one end of the association must map the association, and the other must indicate to Hibernate that it is inverse to the other end using the mappedBy attribute.

Since many-to-many is completely symmetrical, select one of the finite elements for the owner (i.e. the end that displays the association and therefore has the @JoinTable annotation). The other side is simply inverse and therefore does not have the @JoinTable annotation, but has the mappedBy attribute.

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 the inverse of the User.roles association @ManyToMany(mappedBy = "roles") private Set<User> users = new HashSet<User>(); ... } 

Additional notes:

  • targetEntity is not useful because Hibernate knows this because of the generic type Set . It would be helpful if Set was Set<SomeInterface>
  • CascadeType.ALL, of course, is not what you want. Do you want to remove all user roles when deleting a user? What should happen to other users with these roles?
  • You should almost always initialize both ends of a bidirectional connection. Hibernate respects the end of the owner (i.e., the end without the mappedBy attribute) to maintain the connection.
  • All of this is explained in the Hibernate help documentation. Read: it contains useful information and is not difficult to understand.
+9
source

When working with a bi-directional many-to-many association, you must support both ends of the association. In your case, you must add the user to this role. Adding a role to a user is not enough to create a bi-directional association, which you can read in the Java Persistance with Hibernate book:

As always, bidirectional association (no matter how plural) requires you to establish both ends of the association.

+2
source

All Articles