해시 태그 구현 중에 다음과 같이 에러가 발생했다.
Invocation of init method failed; nested exception isjavax.persistence.PersistenceException:[PersistenceUnit: default]
Unable to build Hibernate SessionFactory;nested exception is org.hibernate.loader.MultipleBagFetchException:
cannot simultaneously fetch multiple bags: [com.jiwon.blog.domain.board.Board.tags, com.jiwon.blog.domain.board.Board.replies]
@Entity @Getter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class Board extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob
private String content;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE) // 글이 삭제되면 댓글 모두 삭제
@OrderBy("createdDate")
private List<Reply> replies = new ArrayList<>();
@OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private List<Tag> tags = new ArrayList<>();
public void setUser(User user){
this.user = user;
}
public void update(BoardUpdateRequestDto request) {
this.title = request.getTitle();
this.content = request.getContent();
}
}
찾아보니 OneToMany, ManyToMany인 Bag 두 개 이상을 EAGER로 fetch할 때 발생하는 에러라고 한다.
위 코드에선 OneToMany인 replies와 tags를 EAGER로 fetch해서 발생한 것이었다.
Bag 2개 이상.. Bag란 무엇인가
Bag(Multiset)은 Set과 같이 순서가 없고, List와 같이 중복을 허용하는 자료구조라고 한다.
하지만 자바 컬렉션 프레임워크에서는 Bag가 없기 때문에 하이버네이트에서는 List를 Bag으로써 사용하고 있는 것이다고 한다.
해시 태그는 보통 중복이 발생하지 않으니 List로 된 형식을 Set으로 바꿔서 해결할 수 있는데 프로젝트에선 tagify 라이브러리를 사용해서 해시 태그를 구현했다.
사용해보니 화면에서 값이 넘어올 때 중복 해시 태그가 제거된 상태로 넘어와서 해시 태그도 Set타입이 아닌 List타입으로 사용했었다.
그렇다면 왜 OneToMany, ManyToMany인 Bag 두 개 이상을 EAGER로 fetch할 때 발생하는것인가?
해시 태그를 set자료형으로 바꾸고 실행해보면 MultipleBagFetchException이 발생하지 않는다.
그래서 해결법에 대한 것을 찾아보다가 다음과 같이 정리해주신 분의 블로그를 읽으면서 해결했다. MultipleBagFetchException 발생시 해결 방법
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Board extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob
private String content;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // 글이 삭제되면 댓글 모두 삭제
@OrderBy("createdDate")
private List<Reply> replies = new ArrayList<>();
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<Tag> tags = new HashSet<>();
// 연관관계 편의 메서드 ...(?) 맞나? 잘 모르겠다..
public void addTags(Tag tag){
this.tags.add(tag);
}
public void setUser(User user){ // 연관관계 편의 메서드
this.user = user;
}
public void update(BoardUpdateRequestDto request) {
this.title = request.getTitle();
this.content = request.getContent();
}
}
@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {
@Override
@EntityGraph(attributePaths = {"replies","tags"}) //{}안에 있는 것들을 fetch join해서 가져오겠다.
List<Board> findAll();
Page<Board> findAll(Pageable pageable);
}
...
jpa:
hibernate:
ddl-auto: create # ???? ??? drop? ???
properties:
hibernate:
show_sql: true
format_sql: true
default_batch_fetch_size: 1000 # multibagfetchException 해결
...
reference
(Troubleshooting) Hibernate MultipleBagFetchException 정복하기