N + 1 문제는 JPA 또는 ORM 사용 시, 연관된 엔티티를 조회할 때 발생하는 성능 문제입니다.
이는 하나의 쿼리로 대상 엔티티(1)를 조회한 뒤, 각 엔티티와 연관된 엔티티(N)를 조회하기 위해 추가 쿼리(N번)가 실행되면서 발생합니다.
Member
와 Team
엔티티가 @ManyToOne 관계로 매핑되어 있다고 가정합니다.@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 설정
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
}
Member
엔티티를 전체 조회하고 각 Member
가 속한 Team
의 이름을 출력한다고 가정합니다.List<Member> members = em.createQuery("SELECT m FROM Member m", Member.class)
.getResultList();
for (Member member : members) {
System.out.println(member.getTeam().getName());
}
SELECT m FROM Member m
쿼리: Member
를 조회하는 쿼리가 1번 실행됩니다.getTeam().getName()
호출 시: 각 Member
의 Team
을 조회하기 위해 Team
테이블에 대한 쿼리가 N번 실행됩니다.Member
가 100명이라면, 1 + 100 = 101
개의 쿼리가 실행됩니다.FETCH JOIN
을 사용하면 연관된 엔티티를 한 번에 조회할 수 있습니다.List<Member> members = em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team", Member.class)
.getResultList();
Member
와 Team
을 한 번의 쿼리로 가져옵니다.@EntityGraph
를 활용하면 특정 연관 필드를 조회 시 함께 가져오도록 설정할 수 있습니다.@Entity
public class Member {
@ManyToOne(fetch = FetchType.LAZY)
@EntityGraph(attributePaths = "team")
private Team team;
}
@BatchSize
를 통해 연관 엔티티를 묶어서 조회하도록 설정할 수 있습니다.@BatchSize(size = 10)
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
spring.jpa.properties.hibernate.default_batch_fetch_size=10
Member
를 조회하면 Team
을 10개씩 나누어 총 10번의 쿼리로 조회합니다.JPA의 N + 1 문제는 JPA를 사용하는 개발자라면 반드시 이해하고 해결해야 하는 주요 성능 문제입니다.
문제의 원인을 이해하고, 페치 조인, 엔티티 그래프, 배치 사이즈 설정 등을 적절히 활용하여 최적화하는 것이 중요합니다.
추가 학습 자료