HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
📝
남득윤 학습 저장소
/
JPA
JPA
/
➕
JPA N+1 문제
/
Fetch join 페치 조인을 통해 N+1 문제를 해결해 봅시다!

Fetch join 페치 조인을 통해 N+1 문제를 해결해 봅시다!

 
@Test void 사용자_조회_페치_조인() { String jpql = "select u from User u join fetch u.team"; List<User> resultList = em.createQuery(jpql, User.class).getResultList(); System.out.println("==========================="); for (User user : resultList) { System.out.println("username : " + user.getUsername()); } System.out.println("==========================="); for (User user : resultList) { System.out.println("username = " + user.getUsername()); Team team = user.getTeam(); System.out.println(team.getClass()); System.out.println("user teamName = " + team.getName()); System.out.println(); } }
fetch join 적용 (fetch type 은 상관 없음)
발생 쿼리 로그
Hibernate: select u.id, t.id u.team_id u.username, t.teamname from user u inner join team t on u.team_id=t.id
출력 로그
=========================== username : 짱구 username : 유리 username : 치타 username : 코난 =========================== username = 짱구 class com.study.jpql.domain.Team user teamName = 해바라기반 username = 유리 class com.study.jpql.domain.Team user teamName = 해바라기반 username = 치타 class com.study.jpql.domain.Team user teamName = 장미반 username = 코난 class com.study.jpql.domain.Team user teamName = 어린이 탐정단
페치 조인을 통해 단 한방의 join 쿼리가 발생하고 결과 엔티티의 연관관계를 jpa 가 마법처럼 매핑 해 주었습니다.
 
만약 여기서 fetch 키워드를 jpql에서 제거하면 같은 쿼리가 발생합니다.
하지만 jpa는 이를 채워야 할 근거가 없기 때문에 얻어온 t.teamname 과 같은 team의 필드를 그냥 버립니다.
 
이후에 연관관계의 fetch 옵션 (EAGER/LAZY)에 따라 추가 적인 쿼리를 발생 시킵니다.

컬렉션 페치 조인

팀을 조회 하며 소속된 멤버를 모두 조회하는 @OneToMany의 관계에서의 fetch join도 확인해 보겠습니다.
@Test void 팀_조회_페치_조인() { String jpql = "select t from Team t join fetch t.users"; List<Team> resultList = em.createQuery(jpql, Team.class) .getResultList(); for (Team team : resultList) { System.out.print("team = " + team.getName()); System.out.print(" -> "); for (User user : team.getUsers()) { System.out.print(user.getUsername()+" "); } System.out.println(); } }
Hibernate: select t.id, u.id, t.teamname, u.team_id u.username u.team_id, u.id from team t inner join user u on team0_.id=users1_.team_id
한방 쿼리가 발생하였고 팀 데이터의 컬렉션 연관관계인 users field에 대해 매핑을 잘 해주었습니다.
하지만 데이터를 살펴 보면 뻥튀기가 발생한 것을 알 수 있습니다.
team = 해바라기반 -> 짱구 유리 team = 해바라기반 -> 짱구 유리 team = 장미반 -> 치타 team = 어린이 탐정단 -> 코난
해바라기반이 두개가 되었다!!!
뻥튀기가 발생한 이유는 db 쿼리의 실행결과를 생각해보면 금방 이해할 수 있습니다.
쿼리 실행 결과
t.id
u.id
t.teamname
u.team_id
u.username
1
1
해바라기반
1
짱구
1
2
해바라기반
1
유리
2
3
장미반
2
치타
3
4
어린이 탐정단
3
코난
이 데이터를 그대로 result set에 담아서 풀기 때문에 jpa 레벨에서는 team 데이터의 뻥튀기가 발생할 수 밖에 없습니다. @NtoMany관계의 어쩔 수 없는 숙명입니다.
하이버네이트는 이를 위한 jpql 키워드 distinct를 제공합니다.
 
distinct 키워드는 두가지 역할을 합니다.
  • 쿼리에 distinct 키워드 (sql의 distinct)
  • 어플리케이션 레벨에서 중복 데이터의 제거 (주소 기반)
Hibernate: select distinct t.id, u.id, t.teamname, u.team_id u.username u.team_id, u.id from team t inner join user u on team0_.id=users1_.team_id team = 해바라기반 -> 짱구 유리 team = 장미반 -> 치타 team = 어린이 탐정단 -> 코난
jpql distinct 키워드 적용 시 출력 로그