항해99

항해99 56일차 TIL1 - JPA 즉시 로딩, 지연 로딩

자몽포도 2023. 2. 13. 17:57

즉시 로딩/지연 로딩 개념을 알기 위해서는 우선 JPA의 프록시에 대한 이해가 필요합니다. 항해99 49일차 TIL1 - JPA 프록시는 왜 필요할까? 에서는 JPA에서 프록시 기술을 사용하는 이유에 대해 설명합니다. JPA 프록시를 아예 모른다면 참고해보셔도 좋을 것 같습니다.


아래와 같은 엔티티 클래스가 있다고 할 때 즉시 로딩, 지연 로딩 시 쿼리가 어떻게 나가는지 확인해봅시다.

public class Member {
    @Id @GeneratedValue
    private Long id;
    private String username;
    private int age;
    private LocalDate createDate;
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

@ManyToOne 기본 로딩 전략을 EAGER 입니다.

그래서 Member만을 조회할 때도 아래와 같이 Team을 조회하는 쿼리가 나갑니다.

즉시 로딩

즉시 로딩의 경우에는 프록시 객체를 사용하지 않고 조회 시 연관된 엔티티(join을 통해서, 이게 중요, 즉시 로딩을 피해야 하는 이유이기도 하다.)를 모두 불러옵니다. 그래서 member 와 연관된 team 객체의 타입을 확인해보면 순수 객체인 것을 확인 할 수 있습니다.

 

이제 지연 로딩을 살펴봅시다.

지연 로딩

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;

지연 로딩의 경우에는 team 엔티티를 부르지 않습니다. team 클래스를 확인해보면 프록시 객체인 것을 볼 수 있습니다.

HibernateProxy 객체

team 엔티티의 속성을 실제로 조회할 때 프록시 객체가 초기화되면서 가져옵니다. 


그렇다면 우리는 즉시 로딩을 사용해야할까요 지연 로딩을 사용해야 할까요?

JPA 저자인 영한님은 즉시 로딩을 지양하라고 합니다. 그 이유는 아래와 같습니다.

1. 즉시 로딩은 무조건적으로 Join을 걸어버린다. 이는 성능에 악영향을 미칠 수 있다.

2. 예상치 못한 SQL이 발생할 수 있다. 즉시 로딩은 JPQL에서 N + 1 문제를 일으킬 수 있기 때문이다.

 

여기서 1번 문제는 위에서 확인할 수 있으니 건너 뛰겠습니다.

 

2번의 경우를 보겠습니다. 다음은 JPQL을 사용해서 조회했을 때입니다.

결과를 확인해보시면 이전과 다르게 즉시 로딩에서도 쿼리가 두 번 나가는 것을 확인할 수 있습니다. 

왜 그럴까? JPQL은 결국 SQL로 변환된다. 그럼 아래와 같이 쿼리가 나갈 것이다.

SELECT * FROM Member m where m.id = 1;

그런데 패치 타입을 보니 EAGER다. 그래서 결국 다 불러와야 한다. 그래서 추가 쿼리가 나간다.

SELECT * FROM Team where team_id = m.team_id;

결국 그래서 N + 1 문제를 발생시킨다. 참고로 N + 1 문제는 패치 타입의 문제가 아니다. EAGER, LAZY 모두 발생한다.

 

 

결론입니다.

1. 즉시 로딩과 지연 로딩에 대해 알아보았는데요. 이를 이해하기 위해서는 우선 프록시에 대해 알아야 합니다.

2. 두 패치 전략 중에는 지연 로딩이 합리적일 때가 많습니다. 지연 로딩을 사용하고 연관된 커리를 불러올 때는 Join Fetch, Projection, @EntityGraph 등 N + 1 문제를 해결하여 쿼리를 조회합시다. 이를 통해 성능 개선 혹은 성능 악화를 막을 수 있습니다.

 

정말로 마지막 N + 1을 해결 방식

1. Join Fetch 

2. @Entity Graph

3. Batch Size