반응형
1. em.find() vs em.getReference()
엔티티를 조회할 때 JPA는 두 가지 방식을 제공합니다.
- em.find() ➡️ 즉시 로딩
- 데이터베이스를 통해 실제 엔티티 객체를 바로 조회합니다.
- 즉시 쿼리가 실행됩니다.
- em.getReference() ➡️ 지연 로딩을 위한 가짜 객체(프록시) 반환
- 데이터베이스 조회를 미루는 프록시 객체를 반환합니다.
- 필요할 때까지 쿼리가 실행되지 않고 대기합니다.
2. 프록시란?
프록시 객체는 JPA가 내부적으로 만들어 주는 가짜 객체입니다.
하지만 실제 엔티티 클래스를 상속받아서 만들어지기 때문에 겉으로 보기에는 동일합니다.
3. 프록시의 동작 원리
프록시 객체는 내부적으로 실제 엔티티를 참조(target)로 보관합니다.
- 프록시 객체를 호출하면 → 실제 엔티티의 메소드를 대신 호출해줍니다.
- 처음 사용되는 시점에 DB 조회가 발생하면서 초기화 됩니다.
Member member = em.getReference(Member.class, "id1");
// 아직 DB 조회 X
String name = member.getName();
// 이 시점에 쿼리 실행 → 프록시 초기화
4. 프록시의 특징과 주의사항
프록시는 편리하지만 몇 가지 주의해야 할 특징이 있습니다.
- 초기화는 한 번만 일어난다.
- 프록시는 처음 접근할 때만 DB 조회가 일어나며, 이후에는 캐싱된 엔티티를 사용합니다.
- 프록시가 실제 엔티티로 바뀌는 것은 아니다.
- 초기화되더라도 여전히 프록시 객체입니다.
- 따라서 == 비교 시 주의해야 하며, 타입 체크는 instanceof를 사용하는 것이 안전합니다.
- 영속성 컨텍스트에 엔티티가 이미 존재하면 실제 엔티티 반환.
- em.getReference()를 호출하더라도 이미 1차 캐시에 있으면 실제 엔티티를 반환합니다.
- 준영속 상태에서 문제 발생
- 프록시는 영속성 컨텍스트의 도움을 받아야 동작합니다.
- 만약 준영속(detached) 상태에서 초기화하면 Hibernate는 LazyInitializationException을 던집니다.
💥 LazyInitializationException 발생 상황 예제
import jakarta.persistence.*;
import org.hibernate.Hibernate;
public class ProxyDetachExample {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("examplePU");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 1. Member 엔티티 저장
Member member = new Member();
member.setId("id1");
member.setName("홍길동");
em.persist(member);
em.getTransaction().commit();
em.clear();
// 2. 프록시 객체 조회 (DB 조회 안 함)
Member proxy = em.getReference(Member.class, "id1");
System.out.println("프록시 클래스 = " + proxy.getClass());
// 출력 예: class com.example.Member$HibernateProxy$....
// 3. 영속성 컨텍스트 종료 → 준영속 상태
em.close();
// 4. 준영속 상태에서 프록시 초기화 시도
try {
System.out.println("프록시 이름 = " + proxy.getName()); // ❌ 예외 발생
} catch (Exception e) {
e.printStackTrace();
}
emf.close();
}
}
실행 결과
프록시 클래스 = class com.example.Member$HibernateProxy$...
org.hibernate.LazyInitializationException: could not initialize proxy [com.example.Member#id1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:170)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:310)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
...
즉, 영속성 컨텍스트가 닫힌 상태에서 프록시가 DB 조회를 시도했기 때문에 예외가 발생한 것입니다.
5. 프록시 확인 방법
실무에서는 프록시 객체를 다루다가 “이게 진짜 엔티티인지 프록시인지” 확인해야 할 때가 있습니다.
이때는 아래 방법들을 활용할 수 있습니다.
- 초기화 여부 확인
- PersistenceUnitUtil.isLoaded(entity);
- 클래스 이름 확인
- System.out.println(entity.getClass().getName()); // ...javasist... or HibernateProxy...
- 프록시 강제 초기화
- org.hibernate.Hibernate.initialize(entity);
반응형
'Back end > Spring Project' 카테고리의 다른 글
| [SpringBoot] SQL 쿼리 파라미터 로그 남기는 방법 (0) | 2026.01.02 |
|---|---|
| [Spring] JPA - @MappedSuperclass (0) | 2025.08.27 |
| [Spring] JPA 양방향 연관관계와 연관관계의 주인 (0) | 2025.08.20 |
| [Spring] JPA 준영속 상태(Detached) (0) | 2025.08.15 |
| [Spring] JPA flush (0) | 2025.08.14 |