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

Flo :

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. 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.

To check whether the inverse direction also works the role object gets loaded from the database using a role DAO. But calling getUsers() on the role object just returns an empty set, although it should contain all the users with this role.

I double checked the database table but everything seems all right. User, role and user_role table were all filled correctly.

So why doesn't the role object contain any user?

I'm using 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>();          

    ... 
}

To test I'm using the following code in a 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("[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());
}  

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.

JB Nizet :

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. 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.

Since a many-to-many is completely symmetric, choose one of the end to be the owner (i.e. the end which maps the association, and thus have the @JoinTable annotation). The other side is just the inverse, and thus doesn't have a @JoinTable annotation, but has a 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'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. It would be useful if the Set was a Set<SomeInterface>
  • CascadeType.ALL is certainly not what you want. 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 (i.e. the end without the mappedBy attribute) to persist the association.
  • All of this is explained in the Hibernate reference documentation. Read it: it's full of useful information, and is not hard to understand.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

The left -hand and right hand side of an arithmetic operation must be of type 'any', 'number' or an enum type

Different slicing behaviors on left/right hand side of assignment operator

Should null be checked on the left or right hand side

Kotlin class literals with empty left hand side are not yet supported?

iOS Swift how to make textview/label on left hand side of a view and if there are too many text, it goes under the right view

islice from right-hand side

Left Hand Side , Right Hand Side Compiler Errors in Dart / Flutter List

show the content on the right hand side

How to create operator*(double) for multiplying on both the left and right hand side?

How to pad char array with empty spaces on left and right hand side of the text

How to show the right hand side layout/margin?

Bitwise binary operator for BYTES requires equal length of the inputs. Got 16 bytes on the left hand side and 4 bytes on the right hand side

BCE0051: Operator '<' cannot be used with a left hand side of type 'Object' and a right hand side of type 'float'

Is 'or' used on the right-hand-side of an assignment pythonic?

Generalized makefile rules with multiple variables on both left and right hand side

Sympy right hand side of inequality

How to use substr from right hand side to left hand side? How to convert each 8 characters from a string to an int?

How to get a list of all drools variables defined in condition / left hand side from inside the consequence / right hand side?

Why are there spaces on the left and right hand side of my seekbar?

Get right-hand side of equation

Can the chrome dev tools be on the left hand side rather then the right?

Left join missing left hand side

I want to write from right hand side to left hand side in textview

Is there a way to refer to the left hand side of an expression in the right hand side? i.e. after the =

Is there a keyboard shortcut to switch between the left-hand side and right-hand side of a diff/compare-two-files editor?

Error with <string>.append() in ton-solidity: Different number of components on the left hand side (1) than on the right hand side (0)

Nullish coalescing operator (??) with right hand side 'undefined'

Left Hand Side Conditional Assignment

typecasting on left hand side in C