지연 로딩과 즉시 로딩
- JPA 에서는 연관관계에서 필요한 엔티티만 가져올 수 있는
FetchType
기능을 제공함
@ManyToOne
은 기본 전략이 즉시 로딩이다.(EAGER)
@OneToMany
은 기본 전략이 지연 로딩이다.(LAZY)
- 연관 엔티티가 사용될 때 가져온다.
- 지연 로딩된 객체의 정보를 조회하기 위해선, 반드시 해당 객체가
영속성 컨텍스트에 존재해야 한다.
- 즉,
트랜잭션 환경
이 적용돼야 한다. @Test
@DisplayName("아보카도 피자 조회 - 즉시 로딩")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
System.out.println("아보카도 피자를 주문한 회원 정보 조회");
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
- 지연 로딩인데 트랜잭션 환경이 아니라면 예외가 발생한다.
@Test
@DisplayName("Robbie 고객 조회 실패 - 지연 로딩")
void test3() {
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
System.out.println("Robbie가 주문한 음식 이름 조회");
for (Food food : user.getFoodList()) {
System.out.println(food.getName());
}
}
언제 사용해야 하는가?
지연 로딩
- 연관 엔티티에 직접 접근을 하는 순간, 쿼리 요청을 보내 실제 데이터를 가져옴
- 접근하기 전까진, 프록시 객체로 래핑된 상태(거의 빈 껍데기 수준)
- 연관 관계를 맺은 엔티티에서, 원하는 정보만 효율적으로 가져오기 위해 사용
- 접근할 필요가 없는 연관 엔티티는 굳이 쿼리 요청을 보낼 필요가 없음
즉시 로딩
- 연관 엔티티가 사용될 예정이라면, 쿼리 요청을 두 번 보내는 것보단 한 번에 가져오기 위해 사용
프록시와 즉시 로딩 주의할 점
1. 즉시 로딩은 연관 엔티티가 많을 수록 부담이 크다.
@ManyToOne
연관 엔티티가 5개라면?
- join이 5개가 발생한다. -> 예외가 발생될 가능성이 매우 높다.
2. 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
EntityManager.find()
는 PK를 정해놓고 DB에서 가져오기 떄문에 JPA 내부에서 최적화를 할 수 있다.
- 하지만, JPQL에선 입력 받은 query string이 그대로 SQL로 변환된다.
- 예시) Member 엔티티에 Team 연관 엔티티가 즉시 로딩으로 설정돼있다고 가정
select m from Member m
-> 멤버 N명을 가져옴
- 각 멤버마다 팀이 존재 -> 멤버당 Team 엔티티를 가져오는 쿼리를 날림
- 즉, 멤버 수만큼 팀을 조회하는 쿼리가 N번 발생
- 멤버 가져오는 쿼리 1번 + 각 멤버의 팀 조회하는 쿼리 N번 => N+1 문제
- 해결 방법
- 지연 로딩을 사용해서 필요할 때만 쿼리를 요청
fetch join
을 활용
- EntityGraph, 어노테이션, 배치 사이즈 설정 등의 방법도 존재
참조
https://ict-nroo.tistory.com/132