[JPA] fetch join, EntityGraph, N+1, Pagination, firstResult/maxResults specified with collection fetch; applying in memory

2023. 12. 26. 15:27·JPA
728x90

 

1. 즉시로딩

  • jpql을 우선적으로 select하기 때문에 즉시로딩을 이후에 보고 또다른 쿼리가 날아가 N+1

2. 지연로딩

  • 지연로딩된 값을 select할 때 따로 쿼리가 날아가 N+1

3. Fetch Join

  • 지연로딩의 해결책
  • 사용될 때 확정된 값을 한번에 join에서 select해서 가져옴
  • Pagination이나 2개 이상의 collection join에서 문제가 발생

4. EntityGraph

  • Hibernate의 Jpql 구문에서의 fetch는 존재하지는 않지만 기존과 마찬가지로 fetch join을 통해 바로 조회할 수 있음
@Repository
public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
    
    @NotNull
    @Override
    @EntityGraph(attributePaths = {"questions", "comments"})
    Page<Person> findAll(Specification<Person> spec, Pageable pageable);
}

5. 2개 이상의 Collection join

  • List 자료구조의 2개 이상의 Collection join(~ToMany관계)에서 fetch join 할 경우 MultipleBagFetchException 예외 발생
  • Set자료구조를 사용한다면 해결가능
@Entity //엔티티 정의
@Table(name="person") //사용하지 않으면 클래스 이름이 테이블 이름이 됨
@Getter //lombok getter
@Setter //lombok setter
public class Person {

    @Id //기본키를 의미. 반드시 기본키를 가져야함.
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    @Column(unique = true, length=10) //유일하고 최대 길이가 10.
    private String personName;

    @Column(name="age")
    private int age;


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY ,mappedBy = "person")
    @Fetch(value = FetchMode.JOIN)
    Set<Question> questions;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY ,mappedBy = "person")
    @Fetch(value = FetchMode.JOIN)
    Set<Comment> comments;

 

6. Pagination

  • fetch join시 limit, offset을 통한 쿼리가 아닌 인메모리에 모두 가져와 application단에서 처리하여 OOM 발생
Fetch Join은 N+1 문제를 해결하는 가장 자주 사용되던 방식이다.
하지만, 경고 메시지에서 언급했듯이 페이징 처리시에 사용할 경우 페이징이 전혀 적용되지 않고, 조건에 해당하는 모든 데이터를 가져와 메모리에 올려두고 사용한다.
조건에 해당 하는 데이터 전체를 가져오기 때문에 당연히 성능 상 이슈가 되며, 이를 메모리에 올려두고 페이징을 처리하니 이중으로 성능에 큰 영향을 준다.
  • BatchSize를 통해 필요 시 배치쿼리로 원하는 만큼 쿼리를 날림 -> 쿼리는 날아가지만 N번 만큼의 무수한 쿼리는 발생되지 않음

 

6-1. EntityGraph 사용하는 경우

  • limit, offset 쿼리가 아닌 전체 스캔 쿼리를 인메모리에서 처리
HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
Hibernate: 
    select
        p1_0.id,
        p1_0.age,
        c1_0.person_id,
        c1_0.id,
        c1_0.content,
        c1_0.title,
        p1_0.person_name,
        q1_0.person_id,
        q1_0.id,
        q1_0.content,
        q1_0.title 
    from
        person p1_0 
    left join
        comment c1_0 
            on p1_0.id=c1_0.person_id 
    left join
        question q1_0 
            on p1_0.id=q1_0.person_id 
    where
        p1_0.person_name like replace(?,'\\','\\\\') 
    order by
        p1_0.id
Hibernate: 
    select
        count(p1_0.id) 
    from
        person p1_0 
    where
        p1_0.person_name like replace(?,'\\','\\\\')

 

6-2. BatchSize 설정하는 경우

application.yaml

  • fail_on_pagination_over_collection_fetch
    • 컬렉션 데이터를 메모리 상에서 페이징으로 가져오려고 할때 에러를 발생시킴
  • default_batch_fetch_size
    • in절을 사용하면 where ingredient0_.recipe_id=?이 조건문을 in절 안에 묶어서 한 번에 쿼리할 수 있음
    •  in절에 몇 개의 ID를 받아서 조회할 것인지를 설정해주는 것
spring:
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        query:
          fail_on_pagination_over_collection_fetch: true 
        default_batch_fetch_size: 1000
        show_sql: true
        format_sql: true
@Repository
public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {

    @NotNull
    @Override
    Page<Person> findAll(Specification<Person> spec, Pageable pageable);

}
  • in 절로 N+1이 아닌 3개의 쿼리 실행
Hibernate: 
    select
        p1_0.id,
        p1_0.age,
        p1_0.person_name 
    from
        person p1_0 
    where
        p1_0.person_name like replace(?,'\\','\\\\') 
    order by
        p1_0.id 
    limit
        ?,?
Hibernate: 
    select
        q1_0.person_id,
        q1_0.id,
        q1_0.content,
        q1_0.title 
    from
        question q1_0 
    where
        q1_0.person_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Hibernate: 
    select
        c1_0.person_id,
        c1_0.id,
        c1_0.content,
        c1_0.title 
    from
        comment c1_0 
    where
        c1_0.person_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Hibernate: 
    select
        count(p1_0.id) 
    from
        person p1_0 
    where
        p1_0.person_name like replace(?,'\\','\\\\')

 

 

 

728x90
'JPA' 카테고리의 다른 글
  • [JPA] @Lock 비관적 락, PESSIMISTIC_WRITE, 타임아웃, 트랜잭션
  • [JPA] 변경감지 Dirty Checking, @Transactional, @DynamicUpdate
  • [QueryDSL] join, paging, where, dto Qclass
Karla Ko
Karla Ko
𝘾𝙤𝙣𝙩𝙞𝙣𝙪𝙤𝙪𝙨𝙡𝙮 𝙄𝙢𝙥𝙧𝙤𝙫𝙞𝙣𝙜, 𝘾𝙤𝙣𝙨𝙩𝙖𝙣𝙩𝙡𝙮 𝘿𝙚𝙫𝙚𝙡𝙤𝙥𝙞𝙣𝙜 𝙔𝙚𝙨!
    250x250
  • Karla Ko
    karlaLog
    Karla Ko
  • 전체
    오늘
    어제
    • Total (461)
      • AI (1)
      • Infra (13)
        • Architecture (2)
        • Kubernetes (5)
        • Docker (3)
        • Cloud (1)
        • DevOps (1)
        • Monitoring (1)
      • Message Queue (4)
        • Kafka (2)
        • RabbitMQ (2)
      • Spring (19)
      • JPA (4)
      • Language (9)
        • Kotlin (1)
        • Java (8)
      • Git (4)
      • DB (4)
      • CS (11)
        • OS (8)
        • Network (2)
        • Algorithm (1)
      • Coding Test (392)
        • programmers (156)
        • Graph (43)
        • DP (37)
        • Search (31)
        • Tree (13)
        • Data Structure (26)
        • Combination (12)
        • Implement (18)
        • Geedy (23)
        • Sort (7)
        • Math (21)
        • geometry (2)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    이분탐색
    정렬
    자료구조
    힙
    DFS
    구간합
    덱
    구현
    최대공약수
    재귀
    백준
    그리디
    다익스트라
    DP
    Algorithm
    월간코드챌린지
    동적계획법
    BFS
    플로이드워셜
    그래프
    최소신장트리
    스택
    조합
    트리
    최단거리
    LIS
    알고리즘
    큐
    프로그래머스
    파이썬
  • hELLO· Designed By정상우.v4.10.3
Karla Ko
[JPA] fetch join, EntityGraph, N+1, Pagination, firstResult/maxResults specified with collection fetch; applying in memory
상단으로

티스토리툴바