Skip to content
Advertisement

Avoid duplication on manyToMany relationships with 3 tables

I’m trying to implement a User system with roles and privileges. So I have 5 tables:

  • users
  • roles
  • privileges
  • users_roles
  • roles_privileges

My class user owns the relationship users_roles and the class roles owns the relationship roles_privileges.

To describe my problem lets say I have the following in my database (simplified):

  • users: user
  • roles: 0, 1
  • privileges: a, b, c, d
  • users_roles: (user, 0), (user, 1)
  • roles_privileges: (0,a), (0,b), (0,c), (1,d)

Now if I retrieve the roles from the user I get the following (simplified): [0, 0, 0, 1]

On the list the role 0 appeared 3 times and the role 1 appeared 1. Which happends to correspond to the number of privileges each of the roles have.

I don’t understand why this is happening. Also this only happends when I retrive the user with the function findById from the user repository. When I retrieve the roles with the “authenticated user” this doesn’t happend.

Here’s the code of the 3 entities (simplified):

USER

@Entity @Table(name = "user")
public class User implements UserDetails {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(fetch = FetchType.EAGER) @JoinTable( 
        name = "users_roles", 
        joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
    ) 
    private Collection<Role> roles;
    
    @JsonIgnore public Collection<Role> getRoles() { return this.roles; } // This is the function that is returning duplicateds
}

ROLE

@Entity  @Table(name = "role")
public class Role {
    
    @JsonView(Views.Basic.class)
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(mappedBy = "roles")
    private Collection<User> users;
    
    @ManyToMany(fetch = FetchType.EAGER) @JoinTable(
        name="roles_privileges",
        joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id")
    )
    private Collection<Privilege> privileges;
    
}

PRIVILEGE

@Entity  @Table(name = "privilege")
public class Privilege {
    
    @JsonView(Views.Basic.class)
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(mappedBy = "privileges")
    private Collection<Role> roles;
}

And here is the code of the endpoint that is trying to retrieve the roles from a user:

@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
@ResponseBody @JsonView(Views.Basic.class)
public Response getRolesFromUser(@PathVariable Long id, @AuthenticationPrincipal final User authUser, HttpServletRequest request) {
    if(!authUser.hasRole("admin")) throw (new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You don't have admin privilegies."));
    
    User user = userService.findById(id);
    if(user == null) throw(new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found."));
    
    return Response.builder().status(HttpStatus.OK).message("User's roles retrieved.").data(user.getRoles()).request(request).build();
}

user.getRoles() returns what I told earlier but authUser.getRoles() works fine. I don’t really need to resolve this problem since this is only intended to be used by the administrator to see other users roles and modify them (but I could always do that on the database). But still is a strange behaviour that I cannot understand and I would like to be able to administrate roles from the application and not from the database.

Advertisement

Answer

As a work arround, I tried to use Set instead of Collection for the user roles. It solved my problem.

This work arround, doesn’t solve the question “Why is behaving this way when using Collection?”.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement