1. N+1 문제란?
N+1 문제는 JPA에서 연관된 엔티티를 조회할 때 발생하는 성능 저하 문제 중 하나입니다. 이는 하나의 메인 엔티티를 조회한 후, 연관된 엔티티를 추가로 조회하면서 발생합니다.
예제 코드
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
}
List<Team> teams = em.createQuery("SELECT t FROM Team t", Team.class).getResultList();
for (Team team : teams) {
System.out.println(team.getMembers().size()); // N개의 추가 쿼리 실행
}
위 코드에서 Team을 조회한 후, 각 Team에 속한 Member를 조회하면서 N개의 추가 쿼리가 발생합니다.
2. N+1 문제 해결 방법
(1) LAZY 설정 사용
기본적으로 JPA는 @OneToMany와 같은 컬렉션 연관 관계에 대해 FetchType.LAZY를 적용하는 것이 좋습니다. LAZY 로딩을 사용하면 필요할 때만 연관된 엔티티를 가져와 N+1 문제를 최소화할 수 있습니다.
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members;
FetchType.LAZY를 설정하면 기본적으로 프록시 객체를 반환하며, 실제 엔티티가 필요한 시점에 쿼리가 실행됩니다.
(2) Fetch Join 사용
Fetch Join을 사용하면 한 번의 쿼리로 연관된 엔티티까지 한꺼번에 조회할 수 있습니다.
List<Team> teams = em.createQuery("SELECT t FROM Team t JOIN FETCH t.members", Team.class).getResultList();
(3) Entity Graph 사용
JPA의 @NamedEntityGraph 또는 EntityGraph를 사용하여 동적으로 연관 엔티티를 로딩할 수 있습니다.
@Entity
@NamedEntityGraph(name = "Team.members", attributeNodes = @NamedAttributeNode("members"))
public class Team {
// ...
}
EntityGraph<?> entityGraph = em.getEntityGraph("Team.members");
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.fetchgraph", entityGraph);
List<Team> teams = em.createQuery("SELECT t FROM Team t", Team.class)
.setHint("javax.persistence.fetchgraph", entityGraph)
.getResultList();
(4) Batch Size 조정 (@BatchSize 사용)
Hibernate의 @BatchSize 옵션을 사용하면 연관된 엔티티를 한 번의 쿼리로 묶어서 가져올 수 있습니다.
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
@BatchSize(size = 10)
private List<Member> members;
또는 글로벌 설정으로 적용할 수도 있습니다.
spring.jpa.properties.hibernate.default_batch_fetch_size=10
3. 결론
N+1 문제는 성능 저하를 유발하는 대표적인 문제이지만, LAZY 설정, Fetch Join, Entity Graph, Batch Size 등의 전략을 활용하면 효과적으로 해결할 수 있습니다. 상황에 맞는 최적화 전략을 선택하여 효율적인 JPA 사용을 고려하는 것이 중요합니다.
'Back-End > JPA' 카테고리의 다른 글
JPA 트랜잭션 관리와 @Transactional의 동작 방식 (0) | 2025.03.09 |
---|---|
JPA랑 Hibernate (0) | 2025.03.09 |
JPA에서 일어나는 영속성 컨텍스트의 동작 원리 (1차 캐시, 변경 감지 등) (0) | 2025.03.09 |
JPA와 QueryDSL: 복잡한 쿼리 효율적으로 작성하기 (0) | 2025.03.09 |
Spring Data JPA에서 Native Query 사용법과 주의점 (0) | 2025.03.09 |