Get eagerly collection of entities containing other eagerly got collections

1k views Asked by At

I've got stuck on the M:N relation between entity and strings. An user can have more than one role and each role can be assigned to more than one user. Role is just a string. Roles are contained in table with two columns: roleId and roleName.

I've created two entities, but I'm absolutely unable to made it work. First entity is the user:

@Entity
@Table(name="appUsers")
public class UserEntity {
    @Id
    private String login;
    private String password;
    @OneToMany(fetch=FetchType.EAGER,mappedBy="user") //we always need to load user's roles
    private Collection<UsersToRoles> roles;
    @Transient
    private Collection<String> roleNames;

    public String getLogin() {
        return login;
    }

    public String getPassword() {
        return password;
    }

    @PostLoad
    void prepareRoleNames() {
        roleNames = new HashSet<String>(roles.size());
        for (UsersToRoles mapping : roles)
            roleNames.add(mapping.getNameOfRole());
    }

    public Collection<String> getRoles() {
        return roleNames;
    }
}

The second is entity associated with connecting table:

@Entity
@IdClass(UsersToRolesId.class)
public class UsersToRoles {
    @Id
    @SuppressWarnings("unused")
    @Column(name="login")
    private String login;
    @Id
    @SuppressWarnings("unused")
    @Column(name="roleId")
    private int roleId;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(name="userRoles", joinColumns={@JoinColumn(name="roleId")})
    private List<String> roleName;
    @ManyToOne
    @JoinColumn(name="login")
    @SuppressWarnings("unused")
    private UserEntity user;

    public String getNameOfRole() {
        if (roleName.isEmpty())
            throw new CommonError("Role name for roleId=" + roleId, AppErrors.ACCESSOR_UNAVAILABLE);
        return roleName.get(0);
    }
}

class UsersToRolesId {
    private String login;
    private int roleId;

    /**
     * Implicit constructor is not public. We have to
     * declare public non-parametric constructor manually.
     */
    public UsersToRolesId() {
    }

    @Override
    public int hashCode() {
        return 17*login.hashCode() + 37*roleId;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof UsersToRolesId))
            return false;
        UsersToRolesId ref = (UsersToRolesId)obj;
        return (this.login.equals(ref.login) && this.roleId == ref.roleId);
    }
}

And the problem is, that the roleName collection is always null. I'm unable to get it work. When I make a mistake in table name in @CollectionTable annotation, it still works. The JPA does not fetch the subcollection at all. It makes select from table of user joined with table UsersToRoles, but the join to table userRoles is missing.

Can I ever do that? Can I get eagerly collection of entities containing another eagerly fetched collections?

2

There are 2 answers

0
JB Nizet On BEST ANSWER

Your mapping is completely wrong. UsersToRoles has a roleId column. Thus it refers to a single role. How could it have a collection of role names? The login column is mapped twice in the entity. Moreover, this looks like a simple join table to me, without any other attribute than the roleId and the login, which are foreign keys to the IDs of User and Role, respectively.

You should have two entities : User and Role, with a ManyToMany association using the UsersToRoles table as join table. That's it. The UsersToRoles table should not be mapped as an entity: it's a pure join table.

0
MaDa On

JPA providers usually have a configuration property denoting default eager fetch depth, i.e. hibernate.max_fetch_depth for Hibernate. Check if you can see more when you increase it.

Also, think about your design. Fetching subcollections of a collection eagerly might be a good idea only in limited scenarios (performance-wise). When you annotate your entity like that, you're going to use eager fetching in all use cases. Perhaps you'd be better off with "lazy" and fetching it eagerly only explicitly, with a query with a JOIN FETCH clause?