TIL 2023/12/08 JPA N+1 문제?

YEONGDO·2023년 12월 8일
0

JPA를 사용하면 자주 만나게 되는 것이 N + 1 문제이다.

N+1 문제란?

연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다. 즉, 1번의 쿼리를 날렸을 때 의도하지 않은 N번의 쿼리가 추가적으로 실행되는 것이다. 이를 N+1 문제라고 한다.

EAGER(즉시 로딩)인 경우

JPQL에서 만든 SQL을 통해 데이터를 조회
이후 JPA에서 Fetch 전략을 가지고 해당 데이터의 연관 관계인 하위 엔티티들을 추가 조회
2번 과정으로 N + 1 문제 발생

LAZY(지연 로딩)인 경우

JPQL에서 만든 SQL을 통해 데이터를 조회
JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않음
하지만, 하위 엔티티를 가지고 작업하게 되면 추가 조회가 발생하기 때문에 결국 N + 1 문제 발생

N+1 문제 해결 방법

1. Fetch join

N+1 자체가 발생하는 이유는 한쪽 테이블만 조회하고 연결된 다른 테이블은 따로 조회하기 때문이다. 미리 두 테이블을 JOIN 하여 한 번에 모든 데이터를 가져올 수 있다면 애초에 N+1 문제가 발생하지 않을 것이다.

사실 우리가 원하는 코드는 select * from owner left join cat on cat.owner_id = owner.id 일 것이다. 최적화된 쿼리를 우리가 직접 사용할 수 있다. 그렇게 나온 해결 방법이 FetchJoin 방법이다. 하지만 이는 jpaRepository에서 제공해주는 것은 아니고 JPQL로 작성해야 한다.

Fetch Join(패치 조인)의 단점
Fetch Join도 언뜻보면 유용해보이지만 단점은 있다. 우선은 우리가 연관관계 설정해놓은 FetchType을 사용할 수 없다는 것이다. Fetch Join을 사용하게 되면 데이터 호출 시점에 모든 연관 관계의 데이터를 가져오기 때문에 FetchType을 Lazy로 해놓는것이 무의미하다.

또한, 페이징 쿼리를 사용할 수 없다. 하나의 쿼리문으로 가져오다 보니 페이징 단위로 데이터를 가져오는것이 불가능하다.

2. @Entity Graph

@EntityGraph 의 attributePaths에 쿼리 수행시 바로 가져올 필드명을 지정하면 Lazy가 아닌 Eager 조회로 가져오게 된다. Fetch join과 동일하게 JPQL을 사용하여 query 문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다. 그리고 Fetch join과는 다르게 join 문이 outer join으로 실행되는 것을 확인할 수 있다.

3. BatchSize

하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 이용하면 연관된 엔티티를 조회할 때 지정된 size 만큼 SQL의 IN절을 사용해서 조회한다.

더 다양한 방법이 있지만 이정도 정리면 충분할 거 같다!!

profile
개발 블로그

0개의 댓글