JPA에서는 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany 같은 연관 관계를 맺을 때,
데이터를 언제 로딩할지 결정하는 FetchType 옵션이 있습니다.
- 즉시 로딩(FetchType.EAGER): 연관된 엔티티를 즉시 함께 조회
- 지연 로딩(FetchType.LAZY): 연관된 엔티티를 실제 사용할 때 조회
1️⃣ 왜 기본적으로 LAZY가 권장될까?
✅ 즉시 로딩(FetchType.EAGER)의 문제점
즉시 로딩은 편리하지만 항상 모든 연관된 엔티티를 함께 불러온다는 점이 문제가 됩니다.
즉, 필요 없는 데이터를 가져오거나, 쿼리가 복잡해지면서 성능 저하가 발생할 수 있어요.
❌ EAGER 사용 시 발생할 문제들
1️⃣ 불필요한 데이터까지 한 번에 조회됨
- 예를 들어, User 엔티티가 List<Order>를 즉시 로딩(EAGER)로 설정했다면,
사용자가 로그인할 때마다 모든 주문 내역까지 함께 불러오는 불상사가 생길 수 있어요.
2️⃣ JPQL 사용 시 예기치 않은 추가 JOIN 발생
- SELECT u FROM User u WHERE u.name = 'Kim' 같은 간단한 JPQL을 실행할 때,
연관된 엔티티를 EAGER로 설정했다면, 자동으로 JOIN이 추가되어 성능이 급격히 떨어질 수도 있습니다.
3️⃣ N+1 문제 발생 가능성
- 즉시 로딩이 설정된 연관 엔티티가 또 다른 연관 관계를 가지고 있다면,
select * from order where user_id = ? 같은 쿼리가 반복적으로 실행될 수 있어요.
(N+1 문제라고 불리는 JPA의 대표적인 성능 이슈입니다.)
📌 💡 그래서 기본적으로 LAZY가 권장됨
- LAZY를 사용하면 연관된 데이터를 진짜 필요한 순간에만 가져올 수 있기 때문입니다.
- 즉, 최소한의 데이터만 불러오는 것이 기본 원칙이고,
연관된 엔티티가 꼭 필요할 때만 추가적인 쿼리를 실행하는 방식이 더 효율적이에요.
2️⃣ 그럼 언제 EAGER를 써야 할까?
✅ 즉시 로딩(EAGER)이 적절한 경우
1️⃣ 자주 사용하는 데이터이고, 항상 함께 조회하는 경우
- 예를 들어, User와 UserProfile처럼 거의 같이 쓰이는 데이터라면
지연 로딩(LAZY)보다는 즉시 로딩(EAGER)이 더 적절할 수 있어요.
2️⃣ 연관된 데이터 개수가 많지 않은 경우
- OneToOne 관계에서는 즉시 로딩을 써도 큰 문제가 되지 않는 경우가 많습니다.
(Order와 Payment 같은 관계에서는 EAGER를 사용해도 무방)
3️⃣ 항상 특정 데이터와 함께 사용해야 하는 경우
- 예를 들어, Board와 BoardCategory처럼 특정 데이터는
무조건 함께 사용해야 한다면 즉시 로딩이 편리할 수도 있어요.
3️⃣ 성능 최적화를 위해 추가로 고려할 것들
✅ 페치 조인(Fetch Join) 활용하기
- 즉시 로딩(EAGER)을 사용하지 않고도 연관된 엔티티를 함께 조회하는 방법이 있습니다.
- JPQL에서 fetch join을 활용하면, 필요한 경우에만 연관 데이터를 한 번에 가져올 수 있어요.
예시: EAGER 대신 fetch join으로 최적화
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId")
User findUserWithOrders(@Param("userId") Long userId);
📌 JOIN FETCH를 사용하면 불필요한 추가 쿼리 없이 한 번의 조회로 데이터를 가져올 수 있음!
💡 이렇게 하면 EAGER를 사용하지 않고도 즉시 로딩 효과를 낼 수 있습니다.
✅ 배치 크기 조정(@BatchSize)
- 만약 List<Order>처럼 컬렉션 데이터를 지연 로딩(LAZY)로 가져와야 한다면,
@BatchSize를 이용해 쿼리 개수를 줄일 수 있습니다.
컬렉션 데이터 최적화 (N+1 문제 방지)
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 10) // 💡 한 번에 10개씩 가져오기
private List<Order> orders;
}
📌 이렇게 하면 지연 로딩 시 10개씩 묶어서 한 번에 조회하여 성능을 개선할 수 있어요.
4️⃣ 결론: LAZY를 기본으로, 상황에 따라 EAGER 또는 최적화 적용!
연관 관계 | 기본 설정 | 추천 방식 |
@ManyToOne | LAZY (권장) | fetch join 활용 |
@OneToMany | LAZY (권장) | @BatchSize로 최적화 |
@OneToOne | EAGER 또는 LAZY | 사용 패턴에 따라 선택 |
@ManyToMany | LAZY (권장) | @JoinTable 활용 |
💡 무조건 LAZY만 쓰는 것도 답이 아니고,
💡 무조건 EAGER를 쓰는 것도 답이 아닙니다.
🔹 어떤 데이터가 자주 함께 사용되는지
🔹 쿼리 성능을 어떻게 최적화할 수 있는지
🔹 N+1 문제를 피할 방법이 있는지
이런 요소들을 실제 데이터 사용 패턴에 맞춰 선택하는 것이 가장 중요합니다! 🚀
'개념정복💫 > 스프링 Spring 정복' 카테고리의 다른 글
양방향 매핑 시 무한 루프 문제란? (0) | 2025.02.23 |
---|---|
JPA 일대일(OneToOne) 관계 매핑 (0) | 2025.02.23 |
JPA 페치 타입: 즉시 로딩(EAGER)과 지연 로딩(LAZY) (0) | 2025.02.23 |
@EntityListeners(AuditingEntityListener.class)란? (4) | 2025.02.22 |
JPA 엔티티 날짜/시간 자동 설정 (0) | 2025.02.22 |