Zayson
A to Zayson!
Zayson
전체 방문자
오늘
어제
  • 분류 전체보기 (132)
    • Computer Science (20)
      • Network (4)
      • DB (12)
      • OS (4)
    • Algorithm (32)
      • 완전탐색(Brute-Force) (3)
      • 그리디(Greedy) (6)
      • 투포인터(Two-Pointer) (1)
      • 그래프(Graph) (5)
      • BFS & DFS (9)
      • 구현, 시뮬레이션(Implementation) (5)
      • 다이나믹 프로그래밍(DP) (3)
    • Backend (51)
      • Spring Boot (19)
      • JPA (16)
      • Kafka (2)
      • Java (13)
      • Kotlin (1)
    • DevOps (1)
      • Jenkins (5)
      • Oracle Cloud Infrastructure (1)
      • Kubernetes & Docker (1)
    • Trouble Shooting (3)
      • JPA (1)
      • Spring Boot (2)
    • 회고 (5)
      • 엔빵 프로젝트 포스트 로드맵 (1)
      • 2022년 (4)
    • Kafka (7)
      • Kafka (5)
      • Kafka Connect (2)
    • 기술 서적 (6)
      • 데이터 중심 애플리케이션 설계 (3)
      • 개발자가 반드시 정복해야할 객체 지향과 디자인 패턴 (2)
      • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • 라이브스터디
  • Kafka Connect
  • JPA
  • CS
  • 나 혼자 스프링부트!
  • 그리디
  • dfs
  • Computer science
  • 백준
  • spring boot
  • 구현
  • kafka
  • Backend
  • 완전탐색
  • SpringBoot
  • 관계형 데이터베이스 실전 입문
  • 엔빵프로젝트
  • Java
  • 프로그래머스
  • BFS

최근 글

티스토리

hELLO · Designed By 정상우.
Zayson

A to Zayson!

페치 조인 (Fetch Join)
Backend/JPA

페치 조인 (Fetch Join)

2022. 8. 3. 12:11
  • JPQL에서 성능 최적화를 위해 제공하는 기능이다.
  • 연관된 엔티티나 컬렉션을 한 번의 SQL로 모두 조회하는 기능이다.
  • join fetch를 이용한다.
// jpql
select m from Member m join fetch m.team

// SQL
select m.*, T.* from Member m
inner join team t on m.team_id = t.id

List<Member> resultList = em.createQuery("select m from Member m", Member.class).getResultList();
for (Member member1 : resultList) {
        System.out.println("member1.getUsername() = " + member1.getUsername() + " , teamName = " + member1.getTeam().getName() );
}

 

  • LAZY로 잡힌 연관관계 엔티티는 프록시로 가져오고 조회 시점에 지연 로딩하여 쿼리를 날린다.
  • 회원 조회 쿼리 1번(4개의 Row를 가져온다.) + 회원에 속한 팀 조회 쿼리 N번(최대 4번 발생) → N + 1 문제 발생
  • N + 1 문제를 해결하기 위해 fetch join을 이용하자!
List<Member> resultList = em.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();
for (Member member1 : resultList) {
        // 조회 시점에 Member, Team을 모두 영속성 컨텍스트에 가져왔기 때문에 프록시가 아니다. 
        // getTeam.getName()을 하는 시점에 쿼리가 발생하지 않는다. (지연로딩되지 않는다.)
        System.out.println("member1.getUsername() = " + member1.getUsername() + " , teamName = " + member1.getTeam().getName() );
}

 

  • 1:N 조인의 경우 데이터가 늘어난다. 
  • 컬렉션 페치조인의 경우 동일한 값을 가지면 하나의 영속성 컨텍스트 주소를 갖는다.

 

  • JPQL은 Distinct를 이용해 중복을 제거하는 것이 가능하다.
    • 발생하는 SQL 쿼리에 Distinct를 추가해준다.
    • 애플리케이션에서 엔티티 중복을 제거해준다.
  • 위에서 Team (id = 1, name = 팀A) 엔티티가 중복이기 때문에 JPA가 중복을 제거해준다. (Distinct 2번 기능)
select distinct t from Team t join fetch t.members

 

페치 조인 vs 일반 조인

  • 일반 조인을 실행하면 연관된 엔티티를 같이 조회하지 않기 실제 조회 시점에 지연 로딩한다.
  • fetch join은 연관된 엔티티를 같이 조회하기 때문에 실제 조회 시점에도 지연 로딩이 발생하지 않는다. 즉, 연관된 엔티티만 즉시 로딩한다.
  • 페치 조인은 객체 그래프를 SQL 한 번으로 조회하는 것이다.

 

페치 조인 특징 및 한계

  • 객체 그래프 탐색의 기본적인 정의는 연관된 엔티티의 데이터를 모두 탐색하는 것이다.
  • 페치 조인은 연관된 엔티티를 모두 가져오는 것이기 때문에 페치 조인하는 대상에 별칭을 주지 않는다.
    • 별칭을 주고 where절에 조건을 걸어 데이터를 조회한다면 전체 엔티티를 불러오고자 페치 조인을 사용했지만, 조건에 따라 데이터가 걸러져 전체 엔티티를 못 불러오는 경우가 생길 수 있기 때문이다.
    • 조건을 걸어 조회를 하고 싶은 경우 조회 자체 대상을 페치 조인 대상으로 조회하자.
    • A → B → C 순으로 연관관계가 있는 경우에 B 엔티티에는 별칭을 줘서 C를 페치 조인으로 가져오는 건 사용한 적이 있다. (김영한 강사님 피셜..)
// Team의 연관관계 member는 5개의 데이터를 가지고 있지만 m.age로 인해 데이터가 줄어든다. 
"select t from Team t join t.members m where m.age > 10"

 

  • 둘 이상의 컬렉션은 페치 조인할 수 없다.
    • 1 : N : M → 1 * N * M 개의 데이터가 나올 수 있기 때문에 데이터 정합성 면에서 문제가 생길 수 있다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    • XtoMany의 관계는 데이터가 늘어난 상태로 DB를 조회한다.
    • XtoOne의 관계의 경우 페치 조인을 사용해도 데이터가 늘어나지 않은 상태이기 때문에 페이징이 가능하다.
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징을 한다. → 메모리에서 사용하기 때문에 예기치 못한 에러 발생 가능
    • 해결 방법
      1. 양방향 연관 관계인 경우 XtoMany 탐색이 아닌 xToOne으로 탐색을 하면 페이징 처리를 할 수 있다.
      2. 페치 조인 없이 엔티티를 조회하고 (쿼리 한번 1번 → 컬렉션 데이터 (3개 가정) )) 조회한 컬렉션 엔티티를 루프를 돌면서 조회한다. (쿼리 3번 발생) → 성능적인 측면에서 좋지 않다. (N + 1 문제 발생)
      3. ✨ @BatchSize를 이용한다. 지정한 사이즈만큼 IN절에 PK를 넣어 데이터를 가져온다.
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

List<Team> resultList = em.createQuery("select t from Team t", Team.class)
                    .setFirstResult(0)
                    .setMaxResults(2)
                    .getResultList();

 

  • 지연 로딩보다 페치 조인이 우선순위가 높다.
  • 최적화가 필요한 부분은 페치 조인을 이용한다.
  • 여러 테이블을 조인해서 엔티티와 다른 모양의 데이터 결과를 받는 경우 일반 조인을 사용하고 DTO로 반환받는다.

 

📄 References

김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

반응형
저작자표시 비영리 변경금지 (새창열림)

'Backend > JPA' 카테고리의 다른 글

Spring Data Jpa  (0) 2022.08.22
JPQL 중급  (0) 2022.08.03
JPQL 기본  (0) 2022.07.29
값 타입  (0) 2022.07.26
영속성 전이 (Cascade), 고아 객체  (0) 2022.07.21
    'Backend/JPA' 카테고리의 다른 글
    • Spring Data Jpa
    • JPQL 중급
    • JPQL 기본
    • 값 타입
    Zayson
    Zayson
    공부한 내용을 정리하는 공간

    티스토리툴바