JPA/JPA Basic

[JPA] 지연로딩(Lazy Loading)을 사용해야 하는 이유

IT록흐 2023. 6. 8. 16:37
반응형
 

[JPA] 프록시( Proxy )

위 사진은 영속성 컨텍스트가 동작하는 과정이다. 엔티티 객체를 생성하려면 SELECT문을 DB에 실행해야 한다. APP과 DB는 서로 다른 영역으로 I/O가 발생한다. 잦은 I/O는 성능저하의 원인이 되므로

lordofkangs.tistory.com

 

 

프록시를 다루면서 지연로딩(Lazy Loading)에 대해서 다루어 보았다.

 

A엔티티와 B엔티티는 서로 연관되어 있다. JPA가 A엔티티를 로드하면 A가 참조하는 B엔티티도 로드되어야 한다. 하지만 DB와 잦은 소통은 성능을 저하시킨다.  JPA(하이버네이트)는 성능 최적화를 위해, 연관된 엔티티는 가짜 객체(프록시,Proxy)로 만들어 실제 엔티티 객체 생성을 지연시키는 전략을 구사한다. 이를 지연 로딩(Lazy Loading)이라 부른다. 

 

그런데 만약 B엔티티가 A엔티티와 밀접한 연관이 있다면 지연로딩을 구사할 이유가 없다. 바로 실제 엔티티를 로드해야한다. 이를 위해, JPA는 즉시로딩(Eager Loading) 전략도 구사한다. 개발자는 연관의 깊이에 따라 알맞는 전략을 설정하면 된다.

 

그러나 즉시로딩은 추천되지 않는다.

 

Student 엔티티

@Entity
@Data
public class Student {

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

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    ClassRoom classRoom;
}

 

Student 엔티티를 생성하고 DB에 find()로 조회를 해보았다.

 

Hibernate: 
    select
        student0_.STUDENT_ID as STUDENT_1_1_0_,
        student0_.classRoom_CLASSROOM_ID as classRoo3_1_0_,
        student0_.name as name2_1_0_,
        classroom1_.CLASSROOM_ID as CLASSROO1_0_1_,
        classroom1_.name as name2_0_1_ 
    from
        Student student0_ 
    left outer join
        ClassRoom classroom1_ 
            on student0_.classRoom_CLASSROOM_ID=classroom1_.CLASSROOM_ID 
    where
        student0_.STUDENT_ID=?

 

 

SELECT문이 실행되었는데 Eager 전략으로 연관된 ClassRoom 객체의 필드데이터를 모두 가져왔다. 지금 연관되어 있는 객체가 ClassRoom 하나여서 규모가 작지만 만약 시스템 규모가 커진다면 개발자가 예상치 못할 정도의  SQL문이 만들어질 수 있다. 

 

find()가 아닌 JPQL을 사용하면 문제가 더 이상해진다. 

 

find() 엔티티를 기준으로 JPA가 연관관계를 분석하여 조회를 하기에 그래도 하나의 쿼리에 묶어서 모든 데이터를 가지고 온다. 반면 JPQL은 쿼리를 기준으로 조회하기에 SELECT문이 계속 추가적으로 발생한다. 

 

String jpql = "SELECT o FROM Order o ";
List<Order> orders = entityManager.createQuery(jpql, Order.class)
                                 .getResultList();

 

위 코드는 SELECT문을 수행하여 주문 테이블에 있는 모든 레코드를 가져와 엔티티로 변환 후 List에 저장하는 코드이다. 그런데 만약 주문 엔티티가 주문항목과 Eager전략으로 묶여있다면 각 주문은 주문항목을 가져오기 위해 SELECT문을 추가로 실행한다. 개발자는 주문엔티티를 가져오기 위해 SELECT문 하나를 실행했지만 개발자가 예상치 못한 여러 SELECT문이 추가로 실행되는 것이다. 이를 N+1문제라 부른다. 

 

이처럼 즉시로딩 전략을 사용하면 개발자가 제어할 수 없는 쿼리가 실행되는 불상사가 발생한다. 특히 연관관계에서 가장 많이 사용되는 어노테이션인 @ManyToOne, @OneToOne은 디폴트가 즉시로딩이므로, 지연로딩으로 설정을 바꾸어 줘야 한다. 그러므로 즉시로딩은 가급적 피하고 지연 로딩만 사용하되, 즉시로딩이 필요한 경우 fetch join이나 엔티티그래프 기능을 사용해야 한다. 

 

 fetch join이나 엔티티그래프 기능은 다음에 다루어 보겠다.

 

 


 

 

참고자료

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

반응형