이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


em.find() vs em.getReference()

  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Example {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
        EntityManager em = emf.createEntityManager();

        // 엔티티의 참조를 가져오는 예시
        em.getTransaction().begin();

        // 예시로 사용할 엔티티 클래스와 기본 키
        Long primaryKey = 1L;
        MyEntity entity = em.getReference(MyEntity.class, primaryKey);

        // entity를 실제로 사용하면 데이터베이스 조회가 발생
        System.out.println(entity.getName());

        em.getTransaction().commit();
        em.close();
        emf.close();
    }
}

 

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

프록시 객체의 초기화

  1. 프록시 객체의 메서드를 호출하거나 필드에 접근
  2. 영속성 컨텍스트에 초기화 요청
  3. 영속성 컨텍스트는 DB를 조회하여 데이터를 가져옴
  4. 영속성 컨텍스트는 가져온 데이터를 바탕으로 실제 엔티티를 생성
  5. 프록시 객체가 실제 엔티티의 메서드를 호출하거나 필드에 접근

 

프록시 객체를 사용할 때의 주의사항

1.초기화 시점

  • 프록시 객체는 처음 접근할 때 한 번만 초기화된다.
  • 예를 들어, 프록시 객체의 메서드를 호출하거나 필드에 접근할 때 실제 데이터베이스 조회가 발생하여 초기화된다.

2.프록시 객체와 실제 엔티티의 차이

  • 프록시 객체가 초기화되더라도, 이는 프록시 객체가 실제 엔티티로 변하는 것이 아니다.
  • 초기화된 프록시 객체를 통해 실제 엔티티에 접근할 수 있다.

3.타입 체크 주의사항

  • 프록시 객체는 원본 엔티티 클래스를 상속받는다. 따라서, == 연산자로 타입을 비교하면 실패할 수 있다.
  • 대신, instanceof 연산자를 사용하여 프록시 객체인지 확인할 수 있다.
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

Member member2 = new Member();
member1.setUsername("member2");
em.persist(member2);

em.flush();
em.clear();

Member m1 = em.find(Member.class, member1.getID());
Member m2 = em.getReference(Member.class, member2.getID());

System.out.println("m1 = " + m1.getClass());
System.out.println("m2 = " + m2.getClass());

System.out.println("m1 == m2 = " + (m1 == m2));

 

4.영속성 컨텍스트의 역할

  • 영속성 컨텍스트에 이미 찾고자 하는 엔티티가 존재하면, em.getReference()를 호출해도 실제 엔티티가 반환된다.
  • 즉, 프록시 객체가 아닌 실제 엔티티 인스턴스가 반환된다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);

em.flush();
em.clear();

Member m1 = em.find(Member.class, member.getID());
System.out.println("m1 = " + m1.getClass());

Member m2 = em.getReference(Member.class, member.getID());
System.out.println("m2 = " + m2.getClass());

System.out.println("m1 == m2 = " + (m1 == m2));

 

참고로 JPA에서 영속성 컨텍스트에서 하나의 트랜잭션안에 묶여있다면 진짜 엔티티든, 프록시든 객체 비교시 동일성을 보장한다.

Member member = new Member();
member.setUsername("member1");
em.persist(member);

em.flush();
em.clear();

Member m1 = em.getReference(Member.class, member.getID());
Member m2 = em.find(Member.class, member.getID());

System.out.println("m1 = " + m1.getClass());
System.out.println("m2 = " + m2.getClass());

System.out.println("m1 == m2 = " + (m1 == m2));

 

 

5.준영속 상태에서의 문제

  • 준영속 상태(persistent context의 도움을 받을 수 없는 상태)에서 프록시 객체를 초기화하면 문제가 발생할 수 있다.
  • 이는 LazyInitializationException 등의 예외를 초래할 수 있다.
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

em.flush();
em.clear();

Member m1 = em.getReference(Member.class, member1.getID());
System.out.println("m1 = " + m1.getClass());

em.detach(m1);

m1.getUsername();

 

 

프록시 관련 메서드

PersistenceUnitUtil.isLoaded(Object entity)

프록시 인스턴스의 초기화 여부를 알려주는 메서드이다.

Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

em.flush();
em.clear();

Member m1 = em.getReference(Member.class, member1.getID());

System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(m1));

 

entity.getClass()

프록시 클래스를 확인하는 메서드이다.

Member m1 = em.getReference(Member.class, member1.getID());
System.out.println("m1 = " + m1.getClass());

 

Hibernate.initialize(entity)

프록시 객체를 강제로 초기화하는 메서드이다.

Hibernate.initialize(m1); //강제 초기화

 

참고: JPA 표준은 강제 초기화 없음