JPA의 즉시 로딩과 지연 로딩에 대해 정리해보겠습니다.
1. 도메인 설계
연관 관계를 설명할 때 가장 자주 쓰이는 예시인 Team, Member 도메인을 이용하겠습니다.
둘 다 id와 name만 갖도록 간단하게 설계했고,
Member에서만 N:1 단방향으로 Team을 참조하계 설계했습니다.
2. 즉시로딩
FetchType.EAGER, 즉시로딩으로 설장하고, 팀과 회원을 하나씩 저장한 후, 회원만 조회했을 때 어떻게 조회되는지 확인해보겠습니다.
테스트 코드는 다음과 같습니다.
팀1과 팀1에 속하는 회원1을 저장하고, 영속성 컨텍스트를 한번 비워줍니다.
이후 회원을 조회하고, 회원의 팀의 이름을 출력해보았습니다.
이때 생성된 쿼리를 확인해보겠습니다.
회원을 조회하는 시점에서 left join을 이용하여 팀까지 읽어오는 것을 확인할 수 있고, 또 이후에는 팀의 이름을 출력해도, 별도로 팀을 조회하는 쿼리가 발생하지 않습니다.
3. 지연로딩
이번엔 팀을 로딩하는 시점을 지연로딩, fetch = FetchType.LAZY로 설정해두고, 위 테스트 코드를 그대로 실행해보겠습니다.
이때 생성된 쿼리는 위와 같습니다.
즉시로딩 때와는 다르게,
1. 회원을 조회할 때 팀을 join하지 않습니다.
2. 팀을 참조하는 시점에서야 select 쿼리가 발생합니다.
이처럼 지연로딩을 이용하면, 실제로 참조하는 시점에야 한번 더 쿼리가 날라갑니다.
사용할 때만 조회한다는 점에서 효율적이지만, 쿼리가 두번 필요하다는 점에서는 비효율적입니다.
따라서 지연로딩이나 즉시로딩 중 항상 좋은 것은 없고,
설계나 쓰임에 따라 결정해주는 것이 좋습니다.
따라서 우선 지연로딩으로 설정하고, 쓰임을 보니 대부분의 경우 참조된다면 즉시로딩으로 바꾸는 것이 권장된다고 합니다!
4. 원리
지연로딩이 가능한 이유는 프록시 패턴을 이용하기 때문입니다.
아마 스프링이나 JPA를 사용해보신 분들이라면 알고 계시는 자주 사용되는 디자인 패턴입니다.
앞서 다뤘던 Team을 바탕으로 간단하게 설명하면
1. Team 대신 TeamProxy class를 주입한다.
2. TeamProxy는 진짜 Team의 id를 알고 있다.
-> 지연로딩하는 경우에도, Team의 id는 불러옵니다!
3. Member에서 team.getNamge(실제로는 TeamProxy 객체)를 호출할 때, TeamPorxy가 알고 있는 id로 진짜 Team을 조회하고 그 객체의 .getName을 호출한 결과를 반환한다.
위와 같은 과정으로 지연로딩이 가능한 것입니다.
실제로 앞서 테스트 코드에서 findMember.getTeam().getName()을 findMember.getTeam.getClass() 로 바꿔보겠습니다.
즉시로딩으로 설정한 경우엔 작성한 Team class가 맞지만,
지연로딩으로 설정한 경우엔 Team$HibernateProxy~ 와 같은 프록시 클래스가 조회되는 것을 확인할 수 있습니다.
'JPA' 카테고리의 다른 글
[JPA] default_batch_fetch_size 동작 (0) | 2023.04.12 |
---|---|
[JPA] N+1 해결하기 (2) - OneToMany (0) | 2023.04.12 |
[JPA] 동적쿼리, QueryDSL (0) | 2023.04.07 |
[JPA] N+1 문제 해결하기 (0) | 2023.04.05 |