본문 바로가기

Spring Boot

즉시 로딩(EAGER), 지연 로딩(LAZY)

Spring Data JPA : Eager, Lazy

@OneToOne(cascade, orphanRemoval, fetch = FetchType.LAZY/EAGER)

 

 - JPA에서 로딩 전목이 동작하는 방식, 주의점

 - 로딩 전목?

 - 로딩 전목의 선택 기준, 사례

 - Hibernate에서 N+1문제와 로딩 전목의 연관성, 해결법

 

 

JAP의 로딩 전략 : EAGER, LAZY

JPA에서 연관관계를 가지는 엔티티를 조회할 때, 해당 연관 엔티티를 어떻게 조회할 것인지 결정하는 전략

EAGR(즉시 로딩) : 엔티티를 조회할 때 연관된 엔티티를 함께 즉 - 시 조회함

LAZY(지연 로딩) : 엔티티를 조회할 때 바로 조회 X, 연관된 엔티티는 실제 사용하는 시점에서 쿼리를 내렸을 때 조회함

 

* 실무에서는 LAZY를 기본으로 사용하고, 필요하다면 fetch join이나 EntryGraph 등을 활용하여 조절한다고 함

 

EAGER : 즉시 동작 / 연관 엔티티를 JOIN을 통해 즉시 조회 / 사용이 편리하나 성능을 예측할 수 없음(N+1 위험)

LAZY : 필요할 때 동작 / 실제로 사용할 때 동작함(Proxy 객체) / 성능 제어에 유리하나 처음에는 Null값처럼 동작함

 

 

EAGER(즉시 로딩)

즉~시 로딩은 부모 엔티티를 조회할 때 연관된 자식 엔티티를 즉시 조회함

이 과정에서 join이 자동적으로 수행되며, Hibernate는 결과를 하나의 쿼리로 가져옴

public class User {
	//
    @OneToOne(fetch = FetchType.Eager) // 즉시 로딩
    private UserStatus userStatus;
    //
}

 

User를 조회하면 조회 시점에서 UserStatus도 함께 조회된다.

SELECT u.*, us.*
FROM users AS u
LEFT JOIN user_statuses AS us ON u.profile_id = us.id;
WHERE u.id = 124354646;

 

User를 조회하는 시점에서 쪼인이 이루어진다고 함

 

* join으로 인해 복잡한 객체 그래프를 한번에 가져옴

* 예상치 못한 쿼리 수행으로 성능이 저하될 수 있음

* N+1의 문제 발생 가능성이 높다고 합니다.

 

 

LAZY가 아닌 EAGER을 사용해도 N+1이 왜 발생하는가?

EAGER는 무조건 JOIN해라가 아니다.

조상 클래스를 조회할 때 자손 클래스를 '즉시' 가져오라는 의미임

JPA는 상황에 따라 JOIN을 하지 않을 수 있으며, 별도의 select 쿼리를 사용할 수 있다..

 

컬렉션 관계 특성상, JOIN을 할 수 없음

 - > @ManyToOne, @OneToOne 관계는 EAGER을 사용하여 즉시로딩하면 JOIN이 자주 사용되지만

      @OneToMany는 JOIN을 할 수 없다. 이건 SQL의 구조상 불가능하다고 함

따라서 EAGER이라고 해도 연관된 컬렉션은 N번 조회할 수 밖에 없어서 N+1 문제가 발생해버림

 

ORM 구현체의 한계?
Hibernate는 즉시로딩일 때도 연관객체가 필요한 시점에 별도의 쿼리를 실행할 수 있다..

EAGER는 영속성 컨텍스트 진입 시점에서 무조건 데이터를 가져오려는 의도를 가지지만, 방법은 N번의 SELECT일 수 있다

 

 

따 라 서

LAZY, EAGER 둘 다 N+1 문제를 해결할 수 없다..

LAZY 전략은 사용 시점마다 SELECT를 하여 N+1 문제가 발생할 수 있음 / fetch join으로 문제해결 가능

EAGER 전략은 처음 조회를 할 때 SELECT를 N번 할 수 있음 / fetch join, EntityGraph로 문제를 해결할 수 있음

* fetch join을 통해서 '어떻게 가져오냐'를 명시해야한다.

 

단건 관계를 조회하는 상황에선 LAZY + 필요할 때 fetch join을 씀

컬렉션 관계를 조회할때도 LAZY + 필요할 때 fetch join을 씀

복잡한 조회를 할땐 EntityGraph나 JPQL fetch join을 끌어와서 쓴다

 

 

LAZY(지연 로딩)

조회 시점에서 사용되는게 아니라, 내가 실제로 사용할 때 쿼리가 실행됨

최초 조회시에는 프록시(proxy) 객체만 주입됨

@Entity
public class Member {
	//
    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;
    //
}

 // team 객체를 사용하지 않아서 SELECT가 발생하지 않음, LAZY니까
Member member = memberRepository.findById(1L).get();
 // team을 사용하는 순간 SELECT가 발생한다
Team team = member.getTeam();

// SQL문
select * from member where id = 1;

// team을 엑세스하면
select * from team where id = ?;

 

LAZY, 지연로딩은 필요할때만 조회하기 때문에 성능면에서 유리하다

초기에는 Proxy 객체이다(내부적으로 Hibernate 기술을 사용한다고 함)

* @ManyToOne, @OneToOne 관계에서는 LAZY 사용을 권장한다.

* @OneToMany에서는 무조건 LAZY를 사용해야한다. EAGLE 사용이 SQL 구조상 불가능하기 때문

* 복잡한 조회 최적화를 할 땐 fetch join을 통해 필요한 시점에 join을 직접 지정할 수 있다

* @EntityGraph를 활용할 수 있음

 

  EAGER, 즉시로딩 LAZY, 지연로딩
동작하는 시점 엔티티를 조회하는 즉시 자손이 필요할 때 조회함
언제 사용해야함? 단순한 관계, 성능이 상관없을때 대부분의 실무에서
N+1위험 높음 낮음(발생시 fetch join으로 해소가능)