✅ EAGER vs LAZY 간단 정리
로딩 전략설명
EAGER | 엔티티 조회 시 연관 객체도 무조건 즉시 함께 로딩 |
LAZY | 엔티티 조회 시 연관 객체는 프록시만 생성 → 필요할 때 쿼리 실행 |
🚫 그럼 왜 EAGER는 실무에서 지양할까?
⚠️ 1. 원치 않는 쿼리가 나감
- EAGER는 엔티티를 조회할 때 무조건 연관 객체를 함께 select함
- → 상황에 따라 필요 없는 쿼리도 같이 실행됨
예:
Record record = recordRepository.findById(1L).get();
→ 그런데 category, emotion이 EAGER면
→ 내부적으로 다음과 같은 쿼리가 자동으로 나감:
select * from record where id = 1; select * from category where id = ...; select * from emotion where id = ...;
➡️ "사용하지 않아도 무조건 조회" → 성능 낭비
⚠️ 2. N+1 문제 유발 가능
@ManyToOne(fetch = FetchType.EAGER)을 여러 개 설정해두면,
리스트 조회 시 → 반복적으로 EAGER 연관 필드도 로딩되며 쿼리 폭발(N+1) 발생
List<Record> records = recordRepository.findAll(); for (Record r : records) { System.out.println(r.getCategory().getName()); // 이미 EAGER로 로딩됨 }
→ 하지만 JPA 내부는 여전히 각각의 category에 대해 N개의 쿼리를 날릴 수도 있어요 (JPA 구현체에 따라 다름)
⚠️ 3. 쿼리 제어권을 잃는다
- EAGER는 개발자가 어떤 시점에 무엇을 로딩할지 제어하지 못함
- 반면 LAZY + fetch join은 필요한 순간에 필요한 만큼만 조회 가능
⚠️ 4. 무한 루프 (StackOverflow) 위험
- 양방향 연관관계에서 EAGER 설정 시,
→ toString(), equals(), hashCode() 등에서 양쪽이 서로 참조하며 무한 순환 참조 발생 가능
public class Record { private Category category; // EAGER } public class Category { private List<Record> records; // EAGER }
➡️ toString()만 호출해도 StackOverflowError
⚠️ 5. 테스트 환경에서 예외나 로딩 지연 발생
- JPA 테스트나 Mock 환경에서는 Lazy는 무시되지만,
EAGER는 진짜 쿼리를 날리려 하기 때문에
→ NullPointerException, EntityNotFoundException, 지연 발생
✅ 그럼 실무에서는 어떻게?
상황설정
기본 설정 | 무조건 LAZY 추천 (@ManyToOne, @OneToMany 모두) |
정말 항상 필요할 때만 | 특정 상황에서만 EAGER → 하지만 이건 예외적 상황 |
실제 로딩은? | 필요 시 fetchJoin으로 제어해서 명시적으로 로딩 |
💬 결론
EAGER는 즉시 편하지만,
실무에서는 '언제, 어떻게, 얼마나' 로딩될지 제어가 안 되기 때문에
성능/예외/디버깅/무한루프 등의 문제를 유발할 수 있음
그래서 실무에서는 모든 연관관계는 기본적으로 LAZY로 선언하고,
필요한 곳에서 fetchJoin 등으로 명시적으로 로딩하는 게 베스트 프랙티스
'Back-End > JPA' 카테고리의 다른 글
[JPA] DTO 반환 vs fetchJoin – 언제 어떤 걸 써야 할까? (feat. 연관관계 매핑 전략) (1) | 2025.08.02 |
---|---|
JPA Cascade Type 정리 (1) | 2025.07.28 |
JPA OSIV(Open Session In View) - 현업에서는 어떻게 사용할까? 🤔 (0) | 2025.07.13 |