JPA N+1문제에 대한 고찰

Updated:

N+1 문제


Entity 간의 연관 관계의 매핑이 있을 경우, 전체 쿼리를 조회 할 때 (1 : n에서 1) 연관 관계의 엔티티도 조회해야 해서 결국은 전체 쿼리 수(n개 데이터) + 1(전체 조회 한번)

  • 발생이유
    • jpaRepository에 정의한 인터페이스 메서드를 실행하면
    • JPA는 메서드 이름을 분석해서 JPQL을 생성하여 실행하게 된다
    • JPQL은 findAll()이란 메소드를 수행하였을 때
    • 해당 엔티티를 조회하는 select * from Owner 쿼리만 실행
    • JPQL은 연관관계 데이터를 무시하고 해당 엔티티 기준으로 쿼리를 조회하기 때문



Fetch Type EAGER vs LAZY


FetchType을 변경하는 것은

단지 N+1 발생 시점을

  • LAZY : 연관관계 데이터를 사용하는 시점으로 미룰지,
  • EAGER : 아니면 초기 데이터 로드 시점에 가져오느냐에 차이만 있는 것이다
@Test
void exampleTest() {
    Set<Cat> cats = new LinkedHashSet<>();
    for(int i = 0; i < 10; i++){
        cats.add(new Cat("cat" + i));
    }
    catRepository.saveAll(cats);

    List<Owner> owners = new ArrayList<>();
    for(int i = 0; i < 10; i++){
        Owner owner = new Owner("owner" + i);
        owner.setCats(cats);
        owners.add(owner);
    }
    ownerRepository.saveAll(owners);

    entityManager.clear();

    System.out.println("-------------------------------------------------------------------------------");
    List<Owner> everyOwners = ownerRepository.findAll(); // 연관관계 데이터 로드시점 (EAGER)
List<String> catNames = everyOwners.stream().flatMap(it -> it.getCats().stream().map(cat -> cat.getName())).collect(Collectors.toList()); 
// 연관관계 데이터 사용 시점 (LAZY)
    assertFalse(everyOwners.isEmpty());
}



Fetch Join이나, EntityGraph는 둘다 즉시 로딩이다.

  • Fetch Join은 JPQL로 직접 작성하는 것
    • 실제로는 INNER JOIN문으로 호출됨
    • Fetch Type 사용 불가능
    • 페이징 쿼리 불가능
  • EntityGraph는 LAZY가 아닌 EAGER 로딩
    • OUTER JOIN문으로 호출됨



공통점 : JPQL을 사용하여 JOIN문을 호출함

공통 단점 : Catersian Product 곱으로 호출이 되기 때문에, 중복 데이터가 존재하고 성능이 떨어질 우려가 있음


[해결]

  1. Set을 사용하면 중복된 데이터를 제거할 수 있다.
  2. JPQL에서 distinct을 사용하여 중복된 데이터를 제거한다.

이유 : https://www.inflearn.com/questions/34797

JPA도 SQL의 row 갯수 만큼 맞춰서 리턴한다.

그래서 중복된 데이터가 발생하는 것이다.

  • 모두 지연 로딩을 설정한다.
    • 단, 최적화가 필요한 곳에는 즉시 로딩을 설정한다.
    • Fetch Join을 최적화가 필요한 곳에 쓰자
    • 만약, Fetch Join을 사용할 수 없다면 BatchSize, EntityGraph 등 다른 방법을 고려해봐야 한다



출처 : https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1

Leave a comment