본문 바로가기

JPA/Proxy

즉시로딩과 지연로딩

JPA에는 즉시로딩과 지연로딩이라는 개념이 존재한다!
즉시로딩은 무엇이고, 지연로딩은 무엇인지 알아보자!
또한 이번 글에서 Proxy에 대한 구체적인 설명은 하지 않으니 Proxy가 궁금하면 아래 글을 참고하자!

2021.08.22 - [JPA/Proxy] - Proxy란?

 

Proxy란?

전체코드 github 주소(branch: blog/proxy) JPA에서 Proxy(프록시)란 무엇일까? 한 번 살펴보자!!! getReference EntityManager에서 제공하는 메서드 중 `getReference`라는 메서드가 있다. `getReference` 메서드..

code-mania.tistory.com


github 전체코드(branch: blog/lazeEager)

 

Student.java

package hellojpa;

import lombok.Data;

import javax.persistence.*;

@Entity
@Data
public class Student extends BaseEntity {

    @Id @GeneratedValue
    private Long id;

    private String name;

    private int age;

    @ManyToOne
    @JoinColumn(name = "CLUB_ID")
    private Club club;
}

Club.java

package hellojpa;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Data
public class Club extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "CLUB_ID")
    private Long id;

    private String name;

    private String description;

    @OneToMany(mappedBy = "club")
    private List<Student> students = new ArrayList<>();

}


Student Entity와 Club Entity가 양방향 연관관계 매핑이 되어있다!

그렇다면 Student를 조회할 때 Club도 함께 조회해야 할까?
이 질문에 대한 답은 상황마다 다르다는 것이다!

  • 대부분의 비즈니스 로직에서 Member와 함께 Team을 사용하는 경우
    → select ... join을 통해 Member와 Team을 조회(즉시로딩)하는 것이 유리
  • 대부분의 비즈니스 로직에서 Member만 사용하는 경우
    → select를 통해 Member만 조회(지연로딩)하는 것이 유리

(코드의 전체적인 상태를 파악해야 하는 이유는,
지연로딩과 즉시로딩 설정은 Entity에서 이뤄져서 원할 때마다 수정할 수 있는 부분이 아니기때문이다.)
 

즉시로딩

Student.java - club 즉시로딩 설정

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CLUB_ID")
private Club club;

@ManyToOne의 fetch 옵션에 EAGER를 설정해주었다!
EAGER로 설정하면 Student 조회 시 Club이 즉시로딩된다.
즉시로딩된다는 것은  Student Entity를 조회하면 Student와 Club Entity 모두 조회된다는 것이다.

 

JpaMain.java - Student 조회 코드

Student findStudent = em.find(Student.class, student.getId());
// 실행결과: findStudent = class hellojpa.Club
System.out.println("findStudent = " + findStudent.getClub().getClass());

실행결과

즉시 로딩으로 설정 후 find를 실행해보면, club까지 join문을 통해 한 번에 끌어오는 것을 볼 수 있다.
(쿼리같은 경우 JPA 구현체마다 차이는 조금 있다. 조인을 사용하기도 하고 사용하지 않기도 함. 대부분은 조인 사용)
또한 findStudent.getClub().getClass()의 결과로 원본 Entity가 찍히는 것도 확인가능하다!

지연로딩

Student.java - club 지연로딩 설정

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CLUB_ID")
private Club club;

@ManyToOne의 fetch 옵션에 LAZY를 설정해주었다!
LAZY로 설정하면 Student 조회 시 Club이 지연로딩된다.
지연로딩은  Student를 조회하면 1차적으로 Student Entity만 가져오고,
Club Entity는 실제로 사용하는 시점에 DB에서 가져온다.

 

JpaMain.java - Student 조회 코드

Student findStudent = em.find(Student.class, student.getId());
// 실행결과: findStudent = class Club의 proxy class
System.out.println("findStudent = " + findStudent.getClub().getClass());
System.out.println("club.name = " + findStudent.getClub().getName());

실행결과

지연로딩으로 설정 후 find()를 실행해보면 첫 번째 쿼리에서 Student만 조회해오고 있는 것을 알 수 있다. 
또한 findStudent.getClub().getClass()의 결과로 Club을 상속받아 만들어진 Proxy 클래스가 출력되고 있는 것도 알 수 있다.
그리고 Proxy class의 특성 상 실제로 사용하는 시점에 객체를 초기화한다.
그래서 club.getName()을 호출한 시점에 select ... from Club 쿼리가 날아가는 것을 확인할 수 있다.
이렇게 연관관계가 매핑된 Entity를 실제 사용 시점에 초기화하는 방식이 지연로딩이다.

실무에서는 지연로딩만!!!

실무에서는 가급적 지연로딩만 사용하는 것이 좋다.
한 Entity에서 즉시로딩을 걸어둔 엔티티가 5개 정도만 되어도 join을 5번이나 하는 거대한 쿼리가 나가게 된다.
이는 성능을 저하시킨다.

 

즉시로딩 + JPQL과 N+1

// JPQL과 N+1 알아보기
List<Student> students = em.createQuery("select s from Student s").getResultList();


또한 즉시로딩과 함께 JPQL을 사용하면 N+1이라는 심각한 문제가 발생한다.
fetch 옵션을 Eager로 설정해 Student Entity 조회 시 Club Entity가 즉시로딩되도록 설정해놓았고,
JPQL을 통해 Student를 뽑아오는 쿼리를 JPQL로 작성했다!
그런데 위 로그를 보면 나간 쿼리의 개수는 총 3개이다!

JPQL을 통해 처음에 select .. from Student가 나가는 것은 당연한 것이다!
하지만 그 아래 두 개의 쿼리는 왜 나갈까??
바로 즉시로딩때문이다!
Student Entity와 연관관계를 맺고 있는 Club이 즉시로딩되도록 설정되어있다.
하지만 Student Entity를 JPQL을 통해 조회한 후 Club은 null 상태이다.
이에 JPA는 즉시로딩으로 설정된 Club Entity의 값을 채우기 위해,
DB에 쿼리를 날려 값을 가져오는 것이다.
현재 나의 코드에서 조회된 Student는 2개이고,
이 두 student 객체의 club을 채워주기 위해 select... from Club 쿼리가 두 번 나간 것이다.
즉, JPQL과 즉시로딩을 동시에 실행하면 결과개수 + 1개의 쿼리가 나가는데, 이를 N + 1 문제라고 한다.
(조회한 Club이 영속성 컨텍스트에 존재하면 쿼리는 날아가지 않는다. 즉 N + 1은 최댓값이라고 볼 수 있다.)

 

이러한 성능적인 문제들때문에 실무에서는 가급적 지연로딩을 사용하는 것이 좋다.

참고로 @ManyToOne과 @OneTone은 기본값이 EAGER, @OneToMany와 @ManyToMany는 기본값이 LAZY이다.

 

참고강의: 배달의 민족 개발팀장 김영한 강사님의 JPA 강의

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

Proxy 주의사항  (0) 2021.08.23
Proxy란?  (0) 2021.08.22