자바 디자인 패턴 용어 사전
실무 적용LazyInitializationException · 지연 로딩 · getReference

지연 로딩 프록시

실제 조회를 미루기 위해 대신 놓는 가짜 객체. 세션이 닫힌 뒤 건드리면 예외가 난다.

실제 조회를 미루려고 자리만 채워 둔 가짜 객체. Proxy 패턴의 대표적 실무 사례다.

Order order = em.getReference(Order.class, 1L);   // 프록시만 받는다. SELECT 안 나감
order.getId();       // 식별자는 프록시가 이미 안다 → 여전히 SELECT 없음
order.getAmount();   // ← 여기서 처음 SELECT 가 나간다 (초기화)

어떻게 만들어지나

하이버네이트가 ByteBuddy 로 엔티티를 상속한 서브클래스를 런타임 생성 모든 게터를 오버라이드해 '아직 초기화 안 됐으면 지금 조회' 를 끼워 넣는다

  • 클래스 이름이 Order$HibernateProxy$aB3xY 처럼 찍힌다
  • 디버거에서 이 이름이 보이면 '아직 로딩 안 됨' 이라는 신호

엔티티가 final이면 상속할 수 없어 프록시를 못 만든다 — 그래서 JPA 엔티티에 final을 쓰지 않는다.

LazyInitializationException

could not initialize proxy - no Session

  • 원인 — 세션(트랜잭션)이 닫힌 뒤에 프록시를 건드렸다
    • 전형적으로 컨트롤러나 뷰에서 엔티티를 직렬화할 때 터진다
    • 개발 환경에서는 안 나다가 운영에서 나는 경우도 있다
    • (OSIV 설정 차이 때문)

대응의 우선순위

  • ① 엔티티를 그대로 응답하지 않는다 — DTO 로 변환한다
    • 근본 대응. 응답 스펙이 엔티티 구조에 묶이는 문제도 함께 사라진다
  • ② 필요한 연관을 트랜잭션 안에서 미리 가져온다
    • fetch join · @EntityGraph
  • ③ OSIV(open-in-view) 로 세션을 늘린다
    • 증상은 사라지지만 커넥션을 뷰 렌더링까지 붙들어
    • 커넥션 풀이 마르는 더 큰 문제를 만든다 — 임시방편이다

함께 나타나는 N+1

지연 로딩은 N+1 문제의 원인이기도 하다

  • 주문 100건 조회 → 각 주문의 회원을 건드림 → SELECT 101 번

지연 로딩 자체가 나쁜 것이 아니라 '언제 초기화되는지 코드에서 안 보인다' 는 점이 문제다

  • 조회 시점을 명시하는 fetch join / DTO 조회로 통제한다

프록시와 실제 인스턴스는 클래스가 다르다

Order a = em.getReference(Order.class, 1L);   // 프록시
Order b = em.find(Order.class, 1L);           // 실제 (또는 이미 프록시)

a.getClass() == b.getClass();    // false 일 수 있다
a instanceof Order;              // true (상속했으므로)

그래서 equals 를 이렇게 쓰면 깨진다

  • if (getClass() != o.getClass()) return false; // ❌ 프록시에서 false

이렇게 써야 한다

  • if (!(o instanceof Order other)) return false; // ✅
  • return id != null && id.equals(other.getId());

id 기반 비교를 쓰는 이유이기도 하다 — 프록시는 식별자만은 이미 알고 있다

초기화 여부를 확인하는 법

Hibernate.isInitialized(order);        // 초기화됐는가
Hibernate.initialize(order);           // 지금 초기화한다 (세션이 살아 있어야 한다)

로그로 확인

  • spring.jpa.show-sql=true
  • logging.level.org.hibernate.SQL=DEBUG

예상 못 한 지점에서 SELECT 가 튀어나오면 그곳이 초기화 시점이다 N+1 진단의 출발점이기도 하다

면접 함정

  • "즉시 로딩으로 바꾸면 해결" → N+1이 더 심해지고 필요 없는 데이터까지 끌어온다.
  • "프록시도 엔티티니까 equals가 통한다" → 프록시와 실제 인스턴스는 클래스가 다르다. equalsinstanceof 기반으로 구현해야 한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 실무 적용 심화 — 프레임워크 소스로 읽는 패턴