Spring + Hibernate: Как эффективно объединить две таблицы связей и включить полученные данные в один объект? (Пользователь-ролевой справа)
Укороченная версия
У меня есть базовая настройка, где пользовательская таблица связана с таблицей ролей, а таблица ролей связана с правом. Это оба отношения "многие ко многим". Роли являются динамической сущностью и не представляют интереса для приложения (только для визуальных аспектов). Когда я выбираю пользователя, я хочу вернуть данные в пользовательскую таблицу, включая список имен всех прав.
Чтобы уточнить, это то, что я хочу сделать решение:
Мне удалось получить права на мой пользовательский объект и вернуть их, но это неэффективно из-за дополнительных вызовов запроса, которые hibernate делает после вызова исходного запроса.
Подробная версия
Позвольте мне сначала дать вам некоторую информацию о том, как связаны сущности и как выглядит код. Моя (упрощенная) структура таблицы базы данных выглядит следующим образом:
User.java
@Entity
@Table(name = "user")
public class User {
@Id
@Column(name = "user_id", columnDefinition = "user_id")
private Long userId;
@Transient
private List<String> rights
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "role_id"))
@JsonIgnore
private List<Role> roles;
//Getters and setters
}
Role.java
@Entity
@Table(name = "role")
public class Role {
@Id
@Column(name = "role_id", columnDefinition = "role_id")
private Long roleId;
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = @JoinColumn(name = "user_id", referencedColumnName = "user_id"))
@JsonIgnore
private List<Employee> employees;
@ManyToMany
@JoinTable(
name = "role_right",
joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = @JoinColumn(name = "right_id", referencedColumnName = "right_id"))
@JsonIgnore
private List<Right> rights;
//Getters and setters
}
Right.java
@Entity
@Table(name = "right")
public class Right {
@Id
@Column(name = "right_id", columnDefinition = "right_id")
private Long rightId;
@Column(name = "name", columnDefinition = "name")
private String name;
@ManyToMany
@JoinTable(
name = "role_right",
joinColumns = @JoinColumn(name = "right_id", referencedColumnName = "right_id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "role_id"))
@JsonIgnore
private List<Role> roles;
//Getters and setters
}
Важно знать, что я использую API спецификаций Java для объединения таблиц:
return (root, query, cb) -> {
query.distinct(true);
Join rolesJoin = root.join("roles", JoinType.LEFT);
Join rightsJoin = rolesJoin.join("rights", JoinType.LEFT);
return cb.conjunction();
};
Это создает правильный запрос:
select <columns go here>
from employee user0_
left outer join user_role roles1_ on user0_.user_id=roles1_.user_id
left outer join role role2_ on roles1_.role_id=role2_.role_id
left outer join role_right rights3_ on role2_.role_id=rights3_.role_id
left outer join right right4_ on rights3_.right_id=right4_.right_id
Все выглядело хорошо для меня до сих пор. Но когда я попытался получить имена всех ролей, там выполнялось более двух запросов (считая за страницу и исходный)
//The original code uses lambda for this
for(Role role : user.getRoles()){
for(Right right: role.getRights()){
user.addRight(right.getName());
}
}
Дополнительный запрос выглядит так:
select <column stuff>
from role_right rights0_
inner join right right1_ on rights0_.right_id=right1_.right_id
where rights0_.role_id=?
Это делает звонок очень неэффективным для меня. В этом случае это один пользователь, но с несколькими пользователями это складывается.
Есть ли способ сделать так, чтобы в один запрос помещались имена всех прав в пользовательской сущности без добавления дополнительных запросов?
Вещи, которые я пробовал до сих пор:
С помощью
@SecondaryTable
непосредственно определить столбец из таблицы Right в моей сущности User. Мне не удалось сначала связать роль с пользователем, а затем использовать поля из таблицы ролей, чтобы связать нужную таблицу. Таким образом, в конце я должен добавить аннотацию @SecondaryTable поверх моего объекта User и определить столбцы объекта Right ниже.С помощью
@Formula
в сущности User, чтобы вставить собственный запрос в запрос. Это также не сработало, так как аннотация не понимала, как отобразить все в список прав.
Здесь могут быть другие варианты, или я сделал что-то ужасно неправильное с реализацией вышеупомянутых. Но пока я не знаю, в каком направлении искать решение моей проблемы. Если бы кто-то мог сказать мне, это было бы здорово.
Заранее спасибо,
Робин
1 ответ
Ты используешь Root.join
который просто объединяет таблицы для целей запроса; Ленивые ассоциации в загруженных объектах по-прежнему не будут инициализированы.
Как я вижу, ваше намерение также состоит в том, чтобы инициализировать ленивые коллекции. Для этого вы должны использовать Root.fetch (определенный в методе интерфейса, унаследованном от FetchParent
):
Создайте соединение выборки для указанного атрибута со значением коллекции, используя данный тип соединения.
Однако ваше намерение не является хорошей практикой; не объединяйте несколько коллекций в одном запросе, иначе набор результатов запроса будет разнесен с полным декартовым произведением между объединенными коллекциями. Ваш набор результатов содержит <num of users> * <num of roles per user> * <num of rights per role>
строк. Итак, каждый пользовательский данные повторяется <num of roles per user> * <num of rights per role>
раз в сгенерированном наборе результатов.
Подход, который я считаю лучшим и наиболее простым, заключается в определении размера партии для ленивых ассоциаций.