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?
Your mapping is completely wrong.
UsersToRoles
has aroleId
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
andRole
, with aManyToMany
association using theUsersToRoles
table as join table. That's it. The UsersToRoles table should not be mapped as an entity: it's a pure join table.