[JPA] N+1 문제
이멀젼씨
·2021. 5. 1. 12:52
목적
의도하지 않는 쿼리문이 실행되는 것을 막아 불필요한 네트워크 통신 비용을 줄이기 위함
목차
- JPA의 N+1이란?
- JPA의 기본적인 로딩 방식
- JPA에서 Lazy로딩의 문제점
- 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 |