Spring Data JPA를 이용하다보면 종종 org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags이란 메세지를 보게 된다. 우선 어떤 상황에 나타나는지 한 번 살펴보자.
엄마가 있고, 아들/딸들이 있는데 아들/딸들을 EAGER로 fetch해 올 때 발생한다. (즉, OneToMany, ManyToMany인 Bag 두 개 이상을 EAGER로 fetch할 때 발생한다.) EAGER로 땡겨오면 N+1 쿼리 문제가 존재하기 때문에 fetchType을 전부 LAZY로 바꾼 후 한 방 쿼리로 불러와도 문제는 재발한다.
A is an unordered collection, which can contain duplicated elements. That means if you persist a bag with some order of elements, you cannot expect the same order retains when the collection is retrieved. There is not a “bag” concept in Java collections framework, so we just use a java.util.List corresponds to a . https://stackoverflow.com/questions/13812283/difference-between-set-and-bag-in-hibernate
즉, Bag(Multiset)은 Set과 같이 순서가 없고, List와 같이 중복을 허용하는 자료구조이다. 하지만 자바 컬렉션 프레임워크에서는 Bag이 없기 때문에 하이버네이트에서는 List를 Bag으로써 사용하고 있는 것이다.
그리고 MotherRepository#findAllWithChildrenBy() 메서드를 통해 호출하면 아래와 같은 결과를 볼 수 있다. 일단 날아간 쿼리는 아래와 같다. (혹시나 join 했을 때 자식들이 없을까봐 엄마라도 불러오려고 기본적으로 outer join을 하고 있다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
select mother0_.id as id1_1_0_, daughters1_.id as id1_0_1_, sons2_.id as id1_2_2_, daughters1_.mother_id as mother_i2_0_1_, daughters1_.mother_id as mother_i2_0_0__, daughters1_.id as id1_0_0__, sons2_.mother_id as mother_i2_2_2_, sons2_.mother_id as mother_i2_2_1__, sons2_.id as id1_2_1__ from mother mother0_ leftouterjoin daughter daughters1_ on mother0_.id=daughters1_.mother_id leftouterjoin son sons2_ on mother0_.id=sons2_.mother_id
Set으로 저장한 딸들은 중복없이 잘 불러와졌고, List(Bag)로 저장한 아들들은 중복있이 잘 불러와졌다. (List라고 무조건 중복이 발생하는 건 아니다. 단일 List(Bag)만 Fetch 해오면 중복없이 잘 불러온다.)
이 결과를 Row로 표시해보자면 다음과 같다.
mother.id
daughter.id
son.id
1
1
1
1
1
2
1
1
3
1
2
1
1
2
2
1
2
3
1
3
1
1
3
2
1
3
3
만약 daughters 마저도 중복도 보장이 안 되고, 순서도 보장이 안 됐다면 어떤 기준을 가지고 Row를 매핑할 수 있을까? (뭐, 물론 이 경우에는 될 수도 있겠지만 좀 더 엔터티의 관계가 복잡한 경우에는 매핑이 불가능하거나 너무 복잡해지는 거 아닐까?) 그렇기 때문에 Multiple Bag은 Fetch가 안 되는 게 아닐까 싶다.
실제로 List로 저장한 데이터를 하이버네이트에서는 BagType으로 취급하고 있고, Set으로 저장한 데이터는 SetType으로 취급하고 있다. ListType 클래스도 있긴 한데 언제 어떻게 써야하는지는 잘 모르겠다 ㅠㅠ…