🔎 Entity 조회
🧩 왜 프록시를 쓸까?
지연 로딩(Lazy Loading)으로 불필요한 SELECT를 미룸
→ 실제로 속성에 접근하는 “그 순간”에 쿼리 실행
연관 엔티티를 항상 같이 가져오지 않아도 됨
→ N+1 줄이기/튜닝 전략과 함께 사용
📌 예제: Tutor – Company 관계
Tutor findTutor = em.find(Tutor.class, 1L);
System.out.println(findTutor.getName()); // Tutor 조회
System.out.println(findTutor.getCompany().getName()); // Company까지 접근 시 추가 SQL
Tutor만 필요하다면 굳이 Company까지 바로 조회할 필요 ❌⚔️ em.find() vs em.getReference()
em.find(Entity.class, id)
→ 즉시 DB 조회 후 진짜 엔티티 반환
em.getReference(Entity.class, id)
→ DB 조회 없이 프록시 반환 (필드 접근 시점에 초기화/SELECT)
💡 동일 영속성 컨텍스트에 이미 1차 캐시에 있으면 두 메서드 모두 실제 엔티티 반환 (추가 SELECT 없음)
Tutor proxyTutor = em.getReference(Tutor.class, tutor.getId());
System.out.println(proxyTutor.getClass());
// => Hibernate Proxy 클래스
🤖 Proxy
🧩 Proxy 객체란?

target)에 대한 참조가 들어 있음target을 초기화해서 사용⚙️ 프록시 초기화 흐름

em.getReference()로 프록시 획득
프록시의 메서드(예: getName()) 호출
영속성 컨텍스트에 초기화 요청
DB 조회 수행
실제 엔티티 생성/연결
이후엔 실제 엔티티에 위임 (초기화는 보통 한 번만)
⚡ Proxy 특징
최초 접근 시 한 번만 초기화
이후에는 같은 엔티티로 계속 접근 가능
프록시 객체로 실제 엔티티에 접근 가능
이미 영속성 컨텍스트에 엔티티가 있다면
getReference() 해도 프록시 대신 실제 엔티티 반환
⚠️ 주의: 준영속 상태에서 Proxy 접근 → LazyInitializationException 발생
(영속성 컨텍스트의 도움이 없어서 DB 접근 불가)
Tutor proxyTutor = em.getReference(Tutor.class, tutor.getId());
em.detach(proxyTutor); // 준영속 상태
proxyTutor.getName(); // ❌ LazyInitializationException 발생
🧠 요약 정리
| 구분 | em.find() 🔍 | em.getReference() 🎭 (Proxy) |
|---|---|---|
| 조회 방식 | 즉시 DB 조회 (SQL 실행) | Proxy 객체(가짜 엔티티) 반환 |
| 반환 객체 | 실제 엔티티 | Proxy 객체 (Hibernate가 실제 엔티티 클래스를 상속해 생성) |
| DB 조회 시점 | em.find() 호출 시 바로 실행 | Proxy 속성/메서드 최초 접근 시 실행 |
| 초기화 | 필요 없음 | 최초 한 번만 초기화, 이후 같은 엔티티 사용 |
| 내부 구조 | 엔티티 자체 | Proxy 내부에 target(실제 엔티티 참조) 보관 |
| 비교 특징 | 실제 엔티티이므로 == 비교 가능 | Proxy와 실제 엔티티 == 비교는 실패 ❌ instanceof 비교는 가능 ⭕ |
| 장점 | 즉시 조회가 필요한 경우 적합 | 지연 로딩(Lazy Loading) 가능 → 성능 최적화 |
| 주의 사항 | 항상 SQL 실행 → 불필요한 조회 발생 가능 | 준영속 상태에서 접근 시 LazyInitializationException ⚠️ 잘못 사용 시 N+1 문제 발생 |