
연관 관계가 있는 엔티티를 조회할 경우,
조회된 데이터 개수(N) 만큼 연관 관계의 조회 쿼리가 추가로 발생하는 문제즉, 한 번의 조회로 끝날 것 같지만 실제로는 N+1번의 쿼리가 발생하여 성능 저하를 일으킨다.
글로벌 패치 전략이
즉시 로딩(EAGER)으로 설정된 상태에서findAll()메서드를 사용
글로벌 패치 전략을 지연 로딩(LAZYA)
으로 설정하고findAll()`을 실행하면
N + 1 문제가 발생하지 않는다.이는 연관관계에 있는 엔티티를 실제 객체 대신에 프록시 객체로 생성하여 주입하기 때문
하지만 프록시 객체를 사용할 경우, 실제 데이터가 필요하여 조회하는 쿼리가 발생하고
N + 1 문제가 발생할 수 있다.
EAGER
엔티티를 조회할 때 연관된 엔티티까지 즉시 함께 가져온다.
→ SQL 조인이 강제로 발생.
LAZY
연관된 엔티티는 실제로 접근할 때 쿼리가 발생한다.
→ 기본적으로 권장되는 방식.
findAll()실행 시 메인 엔티티 리스트(N개)가 먼저 조회된다.
이후 각 엔티티의 연관 관계를 EAGER 전략으로 불러오면,
엔티티마다 추가 쿼리가 발생한다.따라서 총 쿼리 수는 1(메인 조회) + N(연관 엔티티 조회)가 되어 N+1 문제가 발생한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
}
List<Member> members = memberRepository.findAll();
-- members 조회
select * from member;
-- members 수만큼 team 조회
select * from team where team.id = ?;
select * from team where team.id = ?;
select * from team where team.id = ?;
Member가 100명이면, Member 조회 1번 + Team 조회 100번 = 총 101번 발생.
JPQL에서 연관 엔티티를 join fetch로 함께 가져와 한 번의 쿼리로 해결한다.
fetch join
일반 join은 단순히 연관된 엔티티를 SQL 조인으로 묶어주는 역할
**fetch join은 연관된 엔티티를 함께 조회하여 영속성 컨텍스트에 로딩한다.
@Query("select m from Member m join fetch m.team")
List<Member> findAllWithTeam();
select m.*, t.*
from member m
join team t on m.team_id = t.id;
→ Member와 Team을 한 번에 가져오기 때문에 N+1 문제가 사라진다.
스프링 데이터 JPA에서 제공하는 기능으
연관 엔티티를 조회 시점에 그래프 형태로 지정하여 fetch join 효과를 낼 수 있다.
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findAllWithTeam();