JPA를 사용할 때 연관관계를 설정하면 데이터를 조회하는 방법에 따라 두 가지 방식으로 로딩됩니다:
1. Lazy Loading (지연 로딩)
2. Eager Loading (즉시 로딩)
이 글에서는 각 로딩 방식의 특징, 실제 쿼리문이 언제 실행되는지를 예제 코드와 함께 설명하겠습니다.
Lazy Loading은 연관된 엔티티 데이터를 즉시 가져오지 않고, 실제 사용할 때 조회하는 전략입니다.
JPA에서는 기본적으로 @OneToMany
, @ManyToOne
관계를 설정할 때 FetchType.LAZY
를 사용합니다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Parent parent;
}
@Transactional
public void lazyLoadingExample(Long parentId) {
// 부모 엔티티 조회
Parent parent = parentRepository.findById(parentId).orElseThrow();
// 여기서 쿼리는 아직 실행되지 않음 (children은 프록시 상태)
System.out.println("Parent name: " + parent.getName());
// 자식 엔티티에 접근하는 시점에서 쿼리 실행
System.out.println("Number of children: " + parent.getChildren().size());
}
parentRepository.findById()
→ 부모 엔티티만 SELECT 쿼리 실행
SELECT * FROM parent WHERE id = ?;
parent.getChildren().size()
→ 자식 엔티티 조회 시점에 쿼리 실행
SELECT * FROM child WHERE parent_id = ?;
장점
단점
LazyInitializationException
이 발생합니다. Eager Loading은 연관된 데이터를 즉시 조회하는 전략입니다.
JPA에서는 FetchType.EAGER
로 설정하면 엔티티를 조회할 때 JOIN 쿼리를 통해 연관된 데이터도 함께 가져옵니다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "parent_id")
private Parent parent;
}
@Transactional
public void eagerLoadingExample(Long parentId) {
// 부모 엔티티 조회
Parent parent = parentRepository.findById(parentId).orElseThrow();
// EAGER 로딩이므로 children도 함께 가져옴
System.out.println("Parent name: " + parent.getName());
System.out.println("Number of children: " + parent.getChildren().size());
}
SELECT p.*, c.*
FROM parent p
LEFT JOIN child c ON p.id = c.parent_id
WHERE p.id = ?;
장점
단점
구분 | Lazy Loading | Eager Loading |
---|---|---|
데이터 로딩 시점 | 실제로 필요할 때 조회 | 엔티티 조회 시점에 즉시 조회 |
쿼리 실행 | 연관 데이터 접근 시 개별 쿼리 실행 | JOIN 쿼리를 통해 한 번에 로딩 |
성능 | 필요할 때만 로딩 → 성능 최적화 가능 | 불필요한 데이터까지 로딩 → 성능 저하 가능 |
N+1 문제 | 발생할 수 있음 (개별 쿼리 실행) | 발생하지 않음 (JOIN 사용) |
활용 사례 | 데이터 사용 빈도가 낮고 효율적 로딩이 필요할 때 | 데이터 사용 빈도가 높고 JOIN이 유리할 때 |
Lazy Loading
Eager Loading
Lazy Loading과 Eager Loading은 각기 다른 장단점을 가지고 있습니다.
따라서 상황에 맞는 로딩 전략을 선택하고, 필요하다면 JPQL의 Fetch Join
을 사용해 쿼리 최적화를 진행하는 것이 좋습니다.