JPA) fetch join과 distinct

Dokuny·2022년 1월 2일
0

join의 N+1 문제

JPA에서 join을 사용해 엔티티를 가져올 경우,
그 엔티티와 연관되어있는 다른 엔티티를 함께 조회하여 가져오지 않는다.

예를 들어, Team 과 Member 엔티티가 존재한다고 가정하자.

Member를 조회하면서 Team도 같이 조회를 하고 싶은 경우

select m from Member m join m.team

join 을 써서 Team을 Member와 같이 가지고 올 경우 연관된 엔티티를 함께 조회하지 않기 때문에

  • Member를 전체 조회하는 쿼리 한 번
  • Member와 연관있는 Team을 조회하는 쿼리 한 번

총, 두 번의 쿼리가 발생한다.
( 이는 Member들이 모두 동일한 Team을 가질 경우이다 )

만약, 수 많은 Member들이 각기 다른 Team을 가진다면?

  • 다수의 Member를 조회하는 쿼리 한 번
  • Member의 수(N)만큼 각기 다른 Team을 조회하는 쿼리 N 번

N + 1 번의 쿼리가 발생한다.

100명의 멤버가 각기 다른 팀을 가지고 있다면 100 + 1 번의 쿼리가 발생한다는 것 이다.

이러한 N+1 문제는 지연로딩과 즉시로딩 둘 다 발생하는 문제이며 성능 상 좋지 못한 영향을 끼친다.

이를 해결하기 위한 방법 중 하나가 JPQLfetch join 이다

JPQL의 fetch join이란?

  • SQL의 조인 종류 X
  • JPQL에서 성능 최적화를 위해 제공하는 기능
  • 연관된 엔티티나 컬렉션SQL 한 번함께 조회하는 기능

예를 들어

select m from Member m join fetch m.team

이렇게 사용하면 쿼리 한번에 팀과 멤버를 가져올 수 있다.

컬렉션 fetch join

만약, 반대로 팀을 조회할 때 멤버를 함께 조회하고 싶다면?

select t from Team t join fetch t.members where t.name = '도쿤'

이걸 돌려보면 값이 '도쿤'이라는 이름을 가진 팀 하나가 나오는게 아니라 값이 '도쿤'이라는 팀에 소속된 멤버의 갯수만큼 나온다.

왜냐하면 결국엔 DB입장에서 fetch join도 inner join을 사용하기 때문에 중복이 발생한다.

이런 경우 distinct 를 사용하면 중복이 제거된다.

distinct

select distinct t from Team t join fetch t.members
  • SQL의 DISTINCT는 중복된 결과를 제거하는 명령
  • JPQL의 DISTINCT의 2가지 기능
    • SQL에 DISTINCT를 추가
    • 애플리케이션에서 엔티티 중복 제거

JPQL이 SQL로 변환될 때 distinct를 추가하여 보내주긴 하지만 TEAM JOIN MEMBER를 한 결과 값은 멤버 수 만큼 나오기 때문에 이를 애플리케이션에서 같은 식별자를 가진 엔티티를 골라 중복제거 해준다.

fetch join의 한계

  • 페치 조인 대상에는 별칭을 줄 수없다.
    • 하이버네이트 구현체에서는 사용이 가능하지만 가급적 사용하지 않는 것이 좋다.
  • 2개 이상의 컬렉션을 한 번에 페치 조인할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.

fetch join의 특징

  • 연관된 엔티티를 SQL 한번에 조회 가능하기에 성능 최적화에 좋다.
  • 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다.
    • 실무에서 글로벌 로딩전략은 모두 지연로딩으로 하는 것이 좋은데, 최적화가 필요한 곳에만 fetch join을 적용하자.

fetch join 정리

  • 모든 것을 fetch join으로 해결할 수 없지만 객체 그래프를 유지할 때 사용하면 효과적이다.
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 다른 결과가 필요한 경우, fetch join 보다는 일반 join을 사용하여 필요한 데이터들만을 조회해 DTO로 반환하는 것이 좋다.
profile
모든 것은 직접 경험해보고 테스트하자

0개의 댓글