문제 상황
- 게시판의 1페이지의 크기가 10일 때 질문 중 10개의 질문을 paging을 통해 출력한다.
@Getter
@Setter
@Entity
@ToString
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList = new ArrayList<>();
public void addAnswer(Answer a) {
a.setQuestion(this);
answerList.add(a);
}
}
...
<td>
<a th:text="${q.subject}" th:href="@{|/question/detail/${q.id}|}"></a>
<span class="text-danger small ms-2"
th:if="${#lists.size(q.answerList) > 0}"
th:text="${#lists.size(q.answerList)}">
</span>
</td>
...
문제
- 기본적으로 spring에서는 객체의 연관관계를 지연 로딩(Lazy Loading)으로 처리하기 때문에, 처음에 답변 리스트를 불러오는 쿼리를 수행한 다음, 답변에 관한 데이터처리가 요청될 때 객체 내용을 가져오게 된다.
- 위 코드에서는
list.size(q.answerList)
에 의해 추가적인 쿼리가 발생하여 N+1문제가 생긴다.
해결
1. batch_fetch_size 늘리기
- 지연 로딩으로 설정된 엔티티들을 일괄적으로 한 번의 쿼리로 가져온다.
- 한 번의 쿼리로 여러 개의 엔티티를 조회한 다음에, 가져오는 엔티티들의 ID를 기반으로 추가 쿼리를 여러 번 수행하여 데이터를 가져오는 것
- 지연 로딩을 유지하면서 성능을 개선
- 한번에 많은 데이터를 가져오므로 메모리 문제와 추가적인 쿼리 실행으로 인한 성능 저하 문제 발생
2. fetch join
- 하나의 쿼리로 여러 엔티티와 연관된 데이터를 가져오는 방식
- 연관된 엔티티를 지연 로딩 없이 즉시 로딩
- 필요하지 않은 데이터와 중복데이터의 발생으로 인한 메모리 문제
비교
- batch_fetch
대량의 데이터를 가져와야 할 때, 즉 엔티티와 연관된 데이터가 많은 경우 사용하기 적합
- fetch join
여러 엔티티를 조인해서 사용해야 할 때 적합