연관 엔티티를 실제로 쓰는 순간까지 조회를 미루는 전략(FetchType.LAZY).
메모리에 무엇이 들어가 있나
Order를 조회하면 order.member 자리에 진짜 Member가 아니라 프록시가 들어간다.
Order 인스턴스
├── id = 7
├── amount = 10000
└── member → Member$HibernateProxy @f9e8 ← 껍데기
├── id = 1 (이것만 갖고 있다)
├── initialized = false
└── target = null ← 아직 비어 있음
프록시는 하이버네이트가 런타임에 만든 Member의 하위 클래스다. 필드는 비어 있고 식별자만 알고 있다.
초기화 흐름
다이어그램 로딩 중…
getMember()까지는 SQL이 나가지 않는다. 그 객체의 내용을 건드리는 순간 나간다.
getId()는 프록시가 이미 갖고 있어 예외적으로 SQL 없이 반환된다.
기본값 — 자주 틀린다
| 연관 | 기본 fetch |
|---|---|
@ManyToOne · @OneToOne | EAGER |
@OneToMany · @ManyToMany | LAZY |
@ManyToOne이 EAGER인 것이 함정이다. 모든 연관을 LAZY로 명시하고 필요할 때만
fetch join으로 가져오는 것이 권장 방식이다.
두 가지 대표 사고
① N+1 — 루프에서 프록시를 건드려 쿼리가 N번 나간다.
② LazyInitializationException — 트랜잭션이 끝나 컨텍스트가 사라진 뒤 프록시를
건드릴 때 난다. 프록시가 초기화를 요청하려면 컨텍스트가 필요한데 이미 없기 때문이다.
컨트롤러·뷰에서 엔티티를 그대로 쓰다 자주 겪는다.
→ 트랜잭션 안에서 필요한 것을 미리 조회하거나 DTO로 변환해서 내보낸다.
프록시 주의
프록시는 원본의 하위 타입이므로
order.getMember().getClass()는Member.class가 아니다instanceof는 통과하지만==클래스 비교는 실패한다equals를 id 기반으로 구현해야 프록시와 실제 엔티티를 같게 볼 수 있다