#jpa #entity-relationship #fetching-strategy
#jpa #сущность-отношение #выборка-стратегия
Вопрос:
Я застрял в отношении M: N между сущностью и строками. У пользователя может быть более одной роли, и каждая роль может быть назначена более чем одному пользователю. Роль — это просто строка. Роли содержатся в таблице с двумя столбцами: roleId
и roleName
.
Я создал две сущности, но я абсолютно не могу заставить их работать. Первый объект — это пользователь:
@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;
}
}
Вторая — это объект, связанный с таблицей соединений:
@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) amp;amp; this.roleId == ref.roleId);
}
}
И проблема в том, что roleName
коллекция всегда равна нулю. Я не могу заставить это работать. Когда я делаю ошибку в имени таблицы в @CollectionTable
аннотации, она все еще работает. JPA вообще не извлекает вложенную коллекцию. Это позволяет выбрать из таблицы пользователя, соединенного с таблицей UsersToRoles
, но соединение с таблицей userRoles
отсутствует.
Смогу ли я когда-нибудь это сделать? Могу ли я получить нетерпеливую коллекцию объектов, содержащих другие нетерпеливо извлеченные коллекции?
Ответ №1:
Ваше отображение совершенно неверно. UsersToRoles
имеет roleId
столбец. Таким образом, это относится к одной роли. Как у него может быть коллекция имен ролей? Столбец login дважды отображается в сущности. Более того, для меня это выглядит как простая таблица соединений, без каких-либо других атрибутов, кроме RoleId и login, которые являются внешними ключами к идентификаторам пользователя и роли соответственно.
У вас должно быть две сущности: User
и Role
, с ManyToMany
ассоциацией, использующей UsersToRoles
таблицу в качестве таблицы соединений. Вот и все. Таблица UsersToRoles не должна отображаться как объект: это чистая таблица соединений.
Ответ №2:
Поставщики JPA обычно имеют свойство конфигурации, обозначающее глубину выборки по умолчанию, т. Е. hibernate.max_fetch_depth
для режима гибернации. Проверьте, сможете ли вы увидеть больше, когда увеличите его.
Кроме того, подумайте о своем дизайне. Извлечение вложенных коллекций коллекции с нетерпением может быть хорошей идеей только в ограниченных сценариях (с точки зрения производительности). Когда вы аннотируете свою сущность таким образом, вы будете использовать нетерпеливую выборку во всех вариантах использования. Возможно, вам было бы лучше использовать «ленивый» и извлекать его только явно, с запросом с предложением JOIN FETCH ?