На Spring пишу мэсседжер, при попытке создать ассоциацию друзей через @OneToMany в зависимости от fetchType выдает разные ошибки
Весь код сущности
@Entity
public class User implements UserDetails {
public User() {
}
public User(String nick, String pass, String name, String surname, String email, boolean isActivated) {
this.username = nick;
this.password = pass;
this.name = name;
this.surname = surname;
this.email = email;
this.isActivated = isActivated;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username, password, name, surname, email;
boolean isActivated;
@OneToMany(targetEntity = Dialogue.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Collection<Dialogue> dialogues = new ArrayList<>();
public Collection<Dialogue> getDialogues() {
return dialogues;
}
public void setDialogues(Collection<Dialogue> dialogues) {
this.dialogues = dialogues;
}
public Collection<User> getFriends() {
return friends;
}
public void setFriends(Collection<User> friends) {
this.friends = friends;
}
@OneToMany(targetEntity = User.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Collection<User> friends = new ArrayList<>();
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isActivated() {
return isActivated;
}
public void setActivated(boolean activated) {
isActivated = activated;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return isActivated();
}
"Больная часть"
@OneToMany(targetEntity = User.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Collection<User> friends = new ArrayList<>();
public Collection<User> getFriends() {
return friends;
}
public void setFriends(Collection<User> friends) {
this.friends = friends;
}
При fetchType Eager выдаёт ошибку
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dialogueRepository' defined in Kejkers.Rgr.repo.DialogueRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot resolve reference to bean 'jpaMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [Kejkers.Rgr.Models.User.dialogues, Kejkers.Rgr.Models.User.friends]
при fetchType LAZY сервер запускается, однако при посещении страницы
@GetMapping("/friends")
public String friends (@AuthenticationPrincipal User user, Model model)
{
Iterable<User> friends = user.getFriends();
model.addAttribute("friends",friends);
return "friends";
}
Всё ломается
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/friends.html]")
при поиске проблемы с помощью точки останов выдает такую строчку
Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
@Transactional пробовал, костыли пробовал.
Ответы (1 шт):
MultipleBagFetchException
Согласно ответу одного из создателей Hibernate, Vlad Mihalcea (https://stackoverflow.com/a/51055523/9978153):
Когда вы ставите fetch = FetchType.EAGER над несколькими @OneToMany полями, вы получаете ошибку MultipleBagFetchException при попытке запросить такую сущность. Это защита от потенциально огромного количества записей, которые придется обработать в рамках запроса из-за двойгого JOIN.
Вам необходимо вернуть fetch = FetchType.LAZY
LazyInitializationException
LazyInitializationException случается, когда вы пытаетесь получить лениво связанные сущности (@OneToMany + FetchType.LAZY), вне сессии. Самый простой способ открыть сессию - использовать @Transactional.
Ваш вопрос содержит упоминание "@Transactional пробовал, костыли пробовал". Было бы замечательно, если бы вы рассказали, где именно вы пробовали установить @Transactional и какие "костыли" вы пробовали.
Решение 1. Возвращать инициализированных пользователей
Судя по всему, Вы используете Spring Security.
Вам необходимо возвращать полностью инициализированных пользователей из UserDetailsService
Для этого можно просто обратиться к списку друзей в рамках сессии, которая возвращаем Вам пользователя.
Но это решение заставит делать запрос за friends всегда, даже когда это на самом деле не нужно.
Решение 2. Использовать отдельный метод репозитория для получения списка друзей.
Это решение является более простым и не требует глубокого погружения.
Можно создать метод в репозитории, который будет возвращать список друзей для пользователя, и не зависеть от возможностей @ObeToMany.
В таком случае можно применить запрос.
SELECT u from User u WHERE :user in u.friends
P.S. Пока я пытался придумать запрос на HQL, я заметил, что User имеет рекурсивную @OneToMany связь (User имeет ссылку на User в поле friends). Не стоит ли заменить связь на @ManyToMany?