
페치 조인은 연관된 엔티티나 컬렉션을 SQL로 한 번에 조회하는 기능으로, JPQL의 성능 최적화를 위해 자주 사용된다. 이 기술을 사용하면 N+1 문제를 해결하거나 데이터 조회 성능을 높이는 데 유용하다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
스프링 데이터 JPA에서는 @EntityGrapth를 사용하여 JPQL 없이도 페치 조인을 할 수 있는 기능을 제공한다.
//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
//메서드 이름으로 쿼리
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
컬렉션을 페치 조인하면 동일한 엔티티가 여러번 중복해서 조회되는 데이터 뻥튀기 문제가 발생한다.
컬렉션 페치 조인 예제
select t
from Team t join fetch t.members
where t.name = ‘팀A'
위 쿼리는 Team과 연관된 Member를 한 번의 SQL로 조회하려고 한다. 하지만, Team A에 속한 두 명의 Member가 있다면 Team A가 중복 조회된다.
위의 상황에서 SQL에 직접 distinct를 추가하면?
→ 모든 값에 대한 데이터가 동일해야 중복을 제거하기 때문에 의도한대로 작동하지 않는다.
JPA는 distinct를 사용하면 추가로 애플리케이션에서 중복 제거를 시도한다.
→ 하이버네이트 6부터는 DISTINCT 없이도 자동으로 중복 제거를 수행한다.
페치 조인과 페이징을 함께 사용할 경우, 데이터 중복으로 인해 메모리에서 페이징을 처리해야 하므로 성능 문제가 발생한다. 이를 해결하려면 다음과 같은 방법을 사용할 수 있다.
BatchSize를 사용하여, 지연 로딩을 최적화할 수 있다. BatchSize는 한 번에 로드할 수 있는 엔티티의 수를 설정하는 방법이다.
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>()
우선 Team을 toOne관계들만 페치 조인하여 페이징 쿼리로 조회한 후, 연관된 Member가 필요해지는 시점에 BatchSize 크기만큼의 Team에 속한 Member를 한 번의 SQL로 로드한다.
즉, ToOne 관계는 페치조인으로 쿼리 수를 줄이고 해결하고, 나머지는 BatchSize로 최적화 하자.
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000
📌사이즈는 순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다. 보통 100~1000 사이 권장!
위와같이 yml 에 최적화 옵션을 설정할 수도 있다.
📌 본 포스팅은 '인프런 - 김영한님 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 실전! 스프링 부트와 JPA 활용)'를 듣고 정리한 내용입니다.