[JPA] 즉시 로딩(EAGER) vs 지연 로딩(LAZY)
Fetch Type이란?
JPA가 하나의 Entity를 조회할 때, 연관 관계에 있는 객체들을 언제, 어떻게 가져올 것인지를 결정하는 설정값 입니다.
- 연관 관계에 있는 모든 Entity를 가져온다 -> EAGER
- 연관 관계에 있는 Entity를 가져오지 않고, getter로 필요한 시점에 접근하여 가져온다 -> LAZY
fetch의 default 값은 @xxToOne에서는 EAGER, @xxToMany에서는 LAZY입니다.
즉시로딩(Eager)
연관 관계에 있는 모든 엔티티를 즉시 가져오는 방식 입니다.
- xxToxx(fetch = fetchType.EAGER)
다음과 같이 Food 엔티티와 User 엔티티가 N:1 매핑으로 관계를 맺고 있습니다. User의 PK가 Foreign Key로 실제 Food DB 컬럼에 매핑되어 있으므로 Food가 주인 입니다.
@Entity
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne // default : 즉시 로딩 사용
@JoinColumn(name="user_id")
private User user;
}
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER) //즉시 로딩 사용
private List<Food> foodList = new ArrayList<>();
}
@SpringBootTest를 사용하여 User, Food 조회
@Test
@Transactional
@DisplayName("FetchType 테스트")
void test4() {
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());
}
}
실제로 실행되는 쿼리문
다음과 같이 즉시 로딩(EAGER) 방식으로 사용하면 User를 조회하는 시점에 바로 Food까지 불러오는 쿼리를 실행하여, 연관 관계에 있는 모든 데이터를 즉시 가져오는 것을 볼 수 있습니다. Robbie가 주문한 음식 이름 조회 부분은 이미 Food 관련 정보가 1차 캐시에 저장되어 있기 때문에 추가 쿼리를 실행하지 않습니다.
지연로딩(Lazy)
연관 관계에 있는 엔티티를 가져오지 않고, getter로 접근하여 필요한 시점에 가져오는 방식 입니다.
- xxToxx(fetch = fetchType.LAZY)
위 예제에서 FetchType만 Lazy로 바꿔서 실행하겠습니다.
@Entity
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 사용
@JoinColumn(name="user_id")
private User user;
}
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String username;
@OneToMany(mappedBy = "user") // default : 지연 로딩 사용
private List<Food> foodList = new ArrayList<>();
}
@SpringBootTest를 사용하여 User, Food 조회
@Test
@Transactional
@DisplayName("FetchType 테스트")
void test4() {
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());
}
}
실제로 실행되는 쿼리문
지연 로딩(LAZY) 방식으로 사용하면 User를 먼저 조회한 후, Food.getName() 가 실행되는 시점에 쿼리가 실행되는 것을 볼 수 있습니다.
그렇다면 어떤 상황에 EAGER 로딩, LAZY 로딩을 사용해야 할까?
비즈니스 로직 상으로 Food 데이터가 필요한 곳에 대부분 User의 데이터가 필요하다면 EAGER 로딩을 사용하여 같이 조회해오는 것이 좋을 것입니다.
Food를 사용하는 곳에 User 데이터가 대부분 필요하지 않는다면? LAZY로 설정하여 Food만 조회하고, 드물게 User가 필요할 때는 그 때 User에 대한 쿼리를 한번 더 날려 조회하는 것이 좋을 것입니다.