241127 TIL - Spring JPA 'N + 1 문제'는 뭐지?

J_log·2024년 11월 27일
0
post-thumbnail

JPA를 공부하면서 자주 등장하던 N+1 문제라는 것에 대해서 정리해보자 !

N+1 문제란?

  1. 하나의 엔티티를 조회하는 쿼리 1개를 실행한다.
  2. 조회한 엔티티 각각에 대해 연관된 엔티티를 추가로 조회하는 쿼리가 실행된다.

결과적으로 쿼리가 1 + N번 실행되므로 N+1 문제라고 부른다 !

Team과 Member가 1:N 관계로 매핑되어 있다고 가정했을 때

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

팀과 그 팀에 소속된 멤버를 출력하는 코드를 이렇게 작성할 수 있다.

public List<Team> findAllTeams() {
    List<Team> teams = teamRepository.findAll(); // (1) Team을 조회하는 쿼리 실행
    for (Team team : teams) {
        team.getMembers().size(); // (2) 각 Team의 Member를 조회하는 쿼리 실행
    }
    return teams;
}
  1. SELECT * FROM team : 모든 팀을 조회하는 1번의 쿼리
  2. SELECT * FROM member WHERE team_id = ? : 각 팀의 멤버를 조회하는 N번의 쿼리

결과적으로 팀이 10개라면 총 11번의 쿼리가 실행된다.


N + 1 문제 해결 방법

  1. fetch 옵션을 사용한 Fetch Join
    @OneToMany나 @ManyToOne 관계를 조회할 때, 연관된 엔티티를 한 번의 쿼리로 가져오도록 설정한다.
@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllTeamsWithMembers();

이 방식은 조인을 통해 Team과 Member를 한 번의 쿼리로 가져오므로 성능 저하를 방지할 수 있다.

  • 실행 쿼리 :
SELECT t.*, m.* 
FROM team t
JOIN member m ON t.id = m.team_id;

  1. EntityGraph 사용
    Spring Data JPA의 @EntityGraph를 활용하면 JPQL을 사용하지 않고도 Fetch Join을 적용할 수 있다.
@EntityGraph(attributePaths = "members")
@Query("SELECT t FROM Team t")
List<Team> findAllTeamsWithMembers();

  1. Batch Size 조정
    Hibernate의 @BatchSize 또는 설정 파일에서 default_batch_fetch_size 옵션을 사용하면 한 번에 여러 엔티티를 로드할 수 있다.
@OneToMany(mappedBy = "team")
@BatchSize(size = 10)
private List<Member> members;

이 방식은 각 팀의 멤버를 한 번에 묶어서 조회하므로 쿼리 수를 줄이는 데 효과적이다.

0개의 댓글