Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JPA N + 1 문제 #21

Open
SuyeonChoi opened this issue Sep 29, 2022 · 0 comments
Open

JPA N + 1 문제 #21

SuyeonChoi opened this issue Sep 29, 2022 · 0 comments
Labels

Comments

@SuyeonChoi
Copy link
Owner

SuyeonChoi commented Sep 29, 2022

N + 1 문제란

처음 실행한 SQL의 결과 수 만큼 추가로 SQL 쿼리가 실행되는 문제

  • 즉시 로딩 N + 1
    • JPQL 사용 시 발생
    • 연관관계 엔티티를 함께 조회할 때, JPQL(em.createQuery("select m from Member m"))을 사용하는 경우 즉시로딩이나 지연 로딩에 대해 신경쓰지 않고 SQL문 실행. 연관된 컬렉션이 즉시 로딩이므로 엔티티를 가져오기 위해 SQL문이 즉시 실행된다
  • 지연 로딩 N + 1
    • JPQL에서는 발생하지 않느다. 하지만 비즈니스 로직에서 실제 하위 컬렉션(엔티티)을 사용하면 지연 로딩이 발생한다. 값을 초기화하는 수만큼 SQL문이 실행된다

해결 방법

  1. 페치 조인
  • 가장 일반적인 방법
  • inner join 문을 생성
  • 단점:
    • Inner Join 문으로 카테시안 곱(Cartesian Product) 발생하여 중복된 결과가 조회될 수 있다.
    • 중복을 제거하기 위해 DISTINCT를 사용
    • 불필요한 쿼리문이 추가될 수 있다.
    • 어떤 필드는 Eager 조회, 어떤 필드는 Lazy 조회까지 쿼리에서 표현해야한다.
  1. @EntityGraph
  • attributePaths에 쿼리 수행시 바로 가져올 필드명을 지정하면 Lazy가 아닌 Eager 조회
  • Outer Join문을 문으로 카테시안 곱(Cartesian Product) 발생하여 중복된 결과가 조회될 수 있다.
  1. 하이버네이트 @BatchSize
  • 연관된 엔티티를 조회할 때 지정한 size 만큼 SQL의 IN 절을 사용해서 조회
  • (조회 가능한 데이터 / size) 수 만큼 SQL이 실행
  • 즉시 로딩으로 설정하면 SQL 문이 모두 한번에 실행되지만, 지연 로딩의 경우 다음 데이터가 필요한 시점에 SQL 쿼리가 추가로 실행된다.
  1. 하이버네이트 @Fetch(FetchMode.SUBSELECT)
  • 연관된 데이터를 조회할 때 서브쿼리를 사용해서 N + 1 문제를 해결

카테시안 곱 해결 방법

  • 일대다 필드의 타입을 Set으로 선언
    • Set은 중복을 허용하지 않는 자료구조이므로 중복등록이 불가
    • LinkedHashSet을 사용하여 순서 보장
  • JPQL DISTINCT 키워드로 중복을 제거
    • Set보다 List를 써야하는 경우 사용하자

정리

  • 즉시 로딩 전략은 그럴듯 보이지만 N + 1 문제는 물론이고 비즈니스 로직에 따라 필요하지 않은 엔티티가 로딩되는 상황이 자주 발생한다
  • 즉시 로딩의 가장 큰 문제는 성능 최적화가 어렵다는 점이다.
  • 엔티티를 조회하다보면 즉시로딩이 연속으로 발생해서 전혀 예상하지 못한 SQL이 실행될 수 있다.
  • 따라서 모두 지연로딩으로 설정하고 성능 최적화가 필요한 곳에 JPQL 페치 조인을 사용하자
  • JPA의 글로벌 페치 전략 기본값
    • @OneToOne, @ManyToOne : 기본 페치 전략은 즉시 로딩
    • @OneToMany, @ManyToMany : 기본 페치 전략은 지연 로딩
    • 기본값이 즉시 로딩인 XXXToOne 은 LAZY로 설정해서 지연로딩 전략을 사용하자

Ref.
[책] 자바 ORM 표준 JPA 프로그래밍
[기억보단기록을 - JPA N+1 문제 및 해결방안]

@SuyeonChoi SuyeonChoi added the JPA label Sep 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant