N+1 문제란 엔티티를 1개 조회했더니 N개의 추가 쿼리문이 실행되는 경우를 말한다.
보통 N+1문제가 발생하는 경우는 연관관계가 복잡한 데이터를 루프하며 접근할때 자주 발생한다.
N+1이 발생할때 생기는 문제는 다음과 같다.
N+1문제가 발생하는 예시 코드는 다음과 같다.
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
}
@Entity
public class Post {
@Id @GeneratedValue
private Long id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
}
다음과 같이 User <-> Post <-> Comment 로 연관관계가 짜여진 엔티티를 3개 생성한다.
이때 특정한 사용자가 작성한 게시글의 댓글을 가져오는 요청을 한다고 가정하면,
public List<PostSummaryDto> getPostSummariesByUser(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException("유저가 없습니다."));
List<PostSummaryDto> result = new ArrayList<>();
for (Post post : user.getPosts()) {
int commentCount = post.getComments().size(); //Lazy 로딩이 발생하는 부분
result.add(new PostSummaryDto(post.getTitle(), commentCount));
}
return result;
}
이런식으로 요청을 할 수 있을것이다.
하지만 이렇게 요청을 하게 된다면, post.getComments() 부분에서 Lazy 로딩이 발생하게 된다.
해당 코드를 실행하면 user.getPosts()를 호출할때 posts를 불러오기 위해
select * from post where user_id = ?;
라는 쿼리문을 실행하게 된다.
그 후,
select * from comment where post_id = ?;
select * from comment where post_id = ?;
select * from comment where post_id = ?;
.
.
.
쿼리를 실행하게 되니 총 1 + 10 = 11 번의 쿼리가 실행되게 된다.
이것이 바로 N+1 문제이다.