항해99 56일차 TIL1 - JPA 즉시 로딩, 지연 로딩
즉시 로딩/지연 로딩 개념을 알기 위해서는 우선 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 클래스를 확인해보면 프록시 객체인 것을 볼 수 있습니다.
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