FETCH JOIN을 사용할 때 제약사항이 있었다.
ToOne 관계 일때는 여러번 사용이 가능했으나, ToMany 관계는 한번만 사용이 가능하다.
Hospital.java
.
.
.
@OneToMany(mappedBy = "hospitals", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
@OrderBy("id asc")
private List<Member> members;
Member.java
.
.
.
@JsonIgnore
@ManyToOne
@JoinColumn(name = "hospitals_id")
private Hospital hospitals;
@OneToMany(mappedBy = "members", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
private List<Equipment> equipments;
Equipment.java
@JsonIgnore
@ManyToOne
@JoinColumn(name = "members_id")
private Member members;
Hospital 안에 Member, Member안에 Equipments 이런 구조를 가지고 있다.
여기 있는 모든 데이터를 조회 하려고 Repository에서 JPQL을 작성하였다.
@Query("SELECT DISTINCT h " +
"FROM Hospital h " +
"LEFT JOIN FETCH h.members m " +
"LEFT JOIN FETCH m.equipments e " +
"ORDER BY h.hospitalName")
List<Team> findAllWithMembersAndEquipments();
실행 하니
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags:
이런 에러가 발생하였다.
그래서 Fetch Join을 제거하고
@Query("SELECT DISTINCT h " +
"FROM Hospital h " +
"LEFT JOIN h.members m " +
"LEFT JOIN m.equipments e " +
"ORDER BY h.hospitalName")
List<Team> findAllWithMembersAndEquipments();
이렇게 수정하니
Hibernate:
select
distinct hospital0_.hospital_id as hospital1_1_,
hospital0_.hospital_address as hospital2_1_,
hospital0_.hospital_code as hospital3_1_,
hospital0_.hospital_name as hospital4_1_,
hospital0_.hospital_number as hospital5_1_
from
hospital hospital0_
left outer join
member members1_
on hospital0_.hospital_id=members1_.hospitals_id
left outer join
equipment equipments2_
on members1_.member_id=equipments2_.members_id
order by
hospital0_.hospital_name
Hibernate:
select
members0_.hospitals_id as hospital5_2_1_,
members0_.member_id as member_i1_2_1_,
members0_.member_id as member_i1_2_0_,
members0_.hospitals_id as hospital5_2_0_,
members0_.member_name as member_n2_2_0_,
members0_.nickname as nickname3_2_0_,
members0_.password as password4_2_0_
from
member members0_
where
members0_.hospitals_id in (
?, ?, ?, ?, ?
)
order by
members0_.member_id asc
Hibernate:
select
equipments0_.members_id as members_4_0_1_,
equipments0_.equipment_id as equipmen1_0_1_,
equipments0_.equipment_id as equipmen1_0_0_,
equipments0_.members_id as members_4_0_0_,
equipments0_.model_name as model_na2_0_0_,
equipments0_.model_serial_no as model_se3_0_0_
from
equipment equipments0_
where
equipments0_.members_id in (
?, ?, ?, ?, ?, ?
)
Hospital, Member, Equipment 세개를 조회할 때 Select 쿼리가 중복되는 N+1 문제가 발생하게 되었다.
해결책은 간단하게, List인 부분을 Set으로 변경한다.
Hospital.java
@OneToMany(mappedBy = "hospitals", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
@OrderBy("id asc")
private Set<Member> members;
Member.java
@OneToMany(mappedBy = "members", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<Equipment> equipments;
Repository
@Query("SELECT DISTINCT h " +
"FROM Hospital h " +
"LEFT JOIN h.members m " +
"LEFT JOIN m.equipments e " +
"ORDER BY h.hospitalName")
@EntityGraph(attributePaths = {"members", "members.equipments"})
Set<Hospital> findAllWithMembersAndEquipments();
그리고 EntityGraph로 가져온다.
결과
Hibernate:
select
distinct hospital0_.hospital_id as hospital1_1_0_,
members1_.member_id as member_i1_2_1_,
equipments2_.equipment_id as equipmen1_0_2_,
hospital0_.hospital_address as hospital2_1_0_,
hospital0_.hospital_code as hospital3_1_0_,
hospital0_.hospital_name as hospital4_1_0_,
hospital0_.hospital_number as hospital5_1_0_,
members1_.hospitals_id as hospital5_2_1_,
members1_.member_name as member_n2_2_1_,
members1_.nickname as nickname3_2_1_,
members1_.password as password4_2_1_,
members1_.hospitals_id as hospital5_2_0__,
members1_.member_id as member_i1_2_0__,
equipments2_.members_id as members_4_0_2_,
equipments2_.model_name as model_na2_0_2_,
equipments2_.model_serial_no as model_se3_0_2_,
equipments2_.members_id as members_4_0_1__,
equipments2_.equipment_id as equipmen1_0_1__
from
hospital hospital0_
left outer join
member members1_
on hospital0_.hospital_id=members1_.hospitals_id
left outer join
equipment equipments2_
on members1_.member_id=equipments2_.members_id
order by
hospital0_.hospital_name,
members1_.member_id asc
N+1문제가 사라졌다.
그런데 사실 Set으로 하기보다는 그냥 다중 엔티티 그래프를 사용하지 않는게 나은것 같다.
왜냐하면 Set은 중복을 제거해주는데 쿼리 할때마다 중복 제거 연산이 발생해서 오버헤드가 나기 때문이다..
이 문제를 어떻게 해결하는게 맞는지 잘 모르겠다.
https://stackoverflow.com/questions/4334970/hibernate-throws-multiplebagfetchexception-cannot-simultaneously-fetch-multipl
https://programmer93.tistory.com/83
https://velog.io/@mingsound21/EntityGraph