[JPA] N+1 문제

이멀젼씨

·

2021. 5. 1. 12:52

목적

의도하지 않는 쿼리문이 실행되는 것을 막아 불필요한 네트워크 통신 비용을 줄이기 위함

목차

  1. JPA의 N+1이란?
  2. JPA의 기본적인 로딩 방식
  3. JPA에서 Lazy로딩의 문제점
  4. JPA의 N+1의 해결 방법

1. JPA의 N+1이란?

1번의 쿼리를 날렸을 때 의도하지 않은 N번의 쿼리가 추가적으로 실행되는 것을 의미한다.

이는 불필요한 네트워크 비용을 증가시킨다.

2. JPA의 기본적인 로딩 방식

JPA는 @ManyToMany, @OneToMany를 사용하는 필드에는 지연로딩을 적용한다.


지연로딩이란?


  • 가능한 한 객체의 초기화를 지연시키는데 사용되는 디자인 패턴이다.
  • 데이터가 불필요한 시점이 아닌 꼭 필요한 시점에 초기화를 시킬 수 있어 시간소비와 메모리 사용량을 줄일 수 있다.

그래서 JPA@**ToMany가 붙어있는 필드의 경우는 필요한 시점에 쿼리를 날려 효율적으로 데이터를 가져올 수 있게 해준다.

3. JPA에서 Lazy로딩의 문제점

Lazy로딩이 무조건 좋다고 볼 수 만은 없다.


필자의 점심 메뉴 추천을 위해 만든 프로그램을 예로 들어보자.


Category와 Restaurant는 1:N관계로 설정하였다.


하나의 카테고리(ex, 중식)아래에 여러개의 음식점(ex, 홍콩반점, 도원 등)이 속할 수 있기 때문이다.


그래서 Category 안에 Restaurant리스트를 만들고 @OneToMany를 붙여주었다.



엔티티를 그대로 반환하지 않고 DTO로 변환해야 했기에 CategoryService에서 getCategoryList를 호출하여 Category를 CategoryDto로 변환하였다.


JPA Repository에서 제공해주는 기본 메서드 findAll을 사용하였고 그 결과 필자의 예상과는 다르게 수많은 쿼리가 실행되었다.


의도하지 않은 11번의 쿼리가 더 발생하였고, 51ms가 걸렸다.

4. JPA의 N+1의 해결 방법

JPQL의 join fetch문을 사용하여 해결하였다.


join fetch는 페치 조인은연관된 엔티티나 컬렉션을 한번에 같이 조회하는 기능이다.



findAll메소드를 레포지토리에 선언하고, 그 위에 @Query를 붙여 JPQL을 사용할 수 있도록 하였다.

35ms가 걸렸다.


그 결과 원하는 대로 1번의 쿼리로 데이터를 가져올 수 있었고, join fetch를 사용하지 않았을 때보다 약 10ms정도 빠르게 측정되었다.

'백엔드 > JPA' 카테고리의 다른 글

[JPA] @DataJpaTest 사용할 DB 변경  (0) 2021.09.16
[JPA] EnumType 올바르게 사용하기  (0) 2021.06.19