[JPA] 페치 조인

jimmy·2025년 3월 20일
post-thumbnail

페치 조인이란?

페치 조인은 연관된 엔티티나 컬렉션을 SQL로 한 번에 조회하는 기능으로, JPQL의 성능 최적화를 위해 자주 사용된다. 이 기술을 사용하면 N+1 문제를 해결하거나 데이터 조회 성능을 높이는 데 유용하다.

페치 조인 특징

  • 지연로딩보다 우선순위가 높다.
  • 페치 조인 대상에는 별칭을 사용할 수 없다.
    → 하이버네이트에서는 가능하지만, 사용을 권장하지 않는다.
  • 둘 이상의 컬렉션을 페치 조인할 수 없다.
    → 컬렉션을 페치 조인한다는 것은 일대다 관계에서의 조인이다. 따라서 둘 이상일 경우 데이터 뻥튀기가 얼마나 될지 예측할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    → 데이터 중복 문제로 인해 JPA는 모든 데이터를 조회한 뒤 메모리에서 페이징 처리를 수행한다.
    즉, 성능 문제가 발생하므로 쓰면 안된다!

페치 조인 사용법

순수 JPA

@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();

스프링 데이터 JPA

스프링 데이터 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가 중복 조회된다.

DISTINCT로 중복 제거

위의 상황에서 SQL에 직접 distinct를 추가하면?

→ 모든 값에 대한 데이터가 동일해야 중복을 제거하기 때문에 의도한대로 작동하지 않는다.

JPA는 distinct를 사용하면 추가로 애플리케이션에서 중복 제거를 시도한다.

→ 하이버네이트 6부터는 DISTINCT 없이도 자동으로 중복 제거를 수행한다.

페이징 문제와 해결 방법

페치 조인과 페이징을 함께 사용할 경우, 데이터 중복으로 인해 메모리에서 페이징을 처리해야 하므로 성능 문제가 발생한다. 이를 해결하려면 다음과 같은 방법을 사용할 수 있다.

해결 방법

  1. BatchSize 설정
  • BatchSize를 사용하여, 지연 로딩을 최적화할 수 있다. BatchSize한 번에 로드할 수 있는 엔티티의 수를 설정하는 방법이다.

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>()
    
  • 우선 TeamtoOne관계들만 페치 조인하여 페이징 쿼리로 조회한 후, 연관된 Member가 필요해지는 시점에 BatchSize 크기만큼의 Team에 속한 Member를 한 번의 SQL로 로드한다.

    즉, ToOne 관계는 페치조인으로 쿼리 수를 줄이고 해결하고, 나머지는 BatchSize로 최적화 하자.

     spring:
    	 jpa:
    		 properties:
    			 hibernate:
    				 default_batch_fetch_size: 1000

    📌사이즈는 순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다. 보통 100~1000 사이 권장!

    위와같이 yml 에 최적화 옵션을 설정할 수도 있다.

  1. 다대일(N:1) 방향으로 쿼리 변경
  • 다대일 관계에서 쿼리를 작성하면 중복 데이터 문제가 발생하지 않는다.

📌 본 포스팅은 '인프런 - 김영한님 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편, 실전! 스프링 부트와 JPA 활용)'를 듣고 정리한 내용입니다.

profile
백문이 불여일기

0개의 댓글