프록시(Proxy)

2022. 5. 25. 13:50Java/JPA

프록시는 그 자체의 개념뿐만 아니라 즉시 로딩, 지연 로딩을 온전히 이해하기 위해 알아두어야 하는 개념입니다.

프록시를 왜 써야 하는지부터 차근차근 알아보도록 하겠습니다.

 

프록시를 왜 써야 하는가?

  • Member를 조회할 때 Team도 함께 DB에서 조회해야 할까?
    • Team을 사용하지 않는 경우 = Team까지 조회를 하는 것은 리소스의 손해이다
  • 해결 : 프록시를 써서 지연로딩을 이용하면, 실제 필요할 때 Team을 DB에서 조회하여 리소스를 절약할 수 있다.

 

프록시

em.find() vs em.getReference()

em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회

em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

  • DB에 쿼리가 안 나가는데 객체가 조회됨

 

프록시 객체의 초기화

// DB를 통해 실제 엔티티 객체 조회, 이 경우 Member와 Team을 Join하여 모두 조회함
Member member = em.find() :

// DB 조회를 미루는 가짜 엔티티 객체 조회(프록시), 이 경우 Member만 조회하며, 
// 이 후 실제 해당 객체를 사용할 때 DB를 조회함
Member proxyMember = em.getReference() :
  • 이때 proxyMember는 실제 Entity의 값을 확인할 수 없으며,
    이를 위해선 아래 영속성 컨텍스트를 통한 초기화 과정이 필요함.
    • 프록시 객체에 요청이 들어왔을 때(그림 1번) target이 없을 경우, 영속성 컨텍스트를 통해
      DB를 조회해서(2~3번) 실제 Entity를 생성한다.(4번)
      그 후 target에 연결해서 진짜 엔티티의 getName()반환한다.(5번)
    • 이 후 같은 객체에 요청을 할 경우, 기 조회된 Entity이기 때문에 다시 DB를 조회하지 않는다.

 

💡 프록시 객체의 특징 💡

1. Proxy는 실제 클래스를 상속받아서 만들어져 기존 클래스와 겉모양이 같다.

  • 사용하는 입장에서는 실제 객체와 프록시 객체와 구분하지 않고 사용 가능하다.

2. 프록시 객체는 실제 객체의 참조를 보관 (target)

3. 처음 사용할 때 한 번만 초기화하고, 이때 프록시 객체가 실제 Entity로 바뀌는 것은 아님

  • 초기화되면 프록시 객체를 통해 실제 엔티티에 접근 가능

4. Type 체크 시 주의 ⇒ 대신 instance of 사용

Member m1 = em.find(Member.class, member1.getId()); // 직접 조회
Member m2 = em.getReference(Member.class, member2.getId()); // 프록시 조회

if(m1.getClass() == m2.getClass()) // False
if(m2 instanceof Member) // True

 

5. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, getReference() 하여도 실제 엔티티 반환

   반대의 경우도 마찬가지이다, 프록시로 처음 조회 했다면 그 후 em.find()해도 프록시 반환

// DB 직접 조회
Member m1 = em.find(Member.class, member1.getId()); 

// 영속성 컨텍스트 내 Entity 조회
Member m2 = em.getReference(Member.class, member1.getId()); 

// 같을까? 힌트 : JPA에서 인스턴스 비교 ==는 같은 영속성 컨텍스트 안에서 조회하면 항상 같다라고 나옴
if(m1 == m2) // True, 둘이 동일 클래스 (Member)
// 이유: 이미 1차캐시에 엔티티 있음 -> 이것을 가져오는 게 성능 최적화면에서도 좋음
// Proxy 조회
Member m1 = em.getReference(Member.class, member1.getId()); 
// DB 직접 조회, 클래스는 HibernateProxy로 반환
Member m2 = em.find(Member.class, member1.getId());

if(m1 == m2) // True, 둘이 동일 클래스 (HibernateProxy)

 

6. 영속성 컨텍스트의 도움을 받을 수 없는 ‘준영속 상태’ 일 때, 프록시 초기화 시 문제 발생

  • (Transaction과 영속성컨텍스트의 시작과 끝을 주로 맞추므로) Transaction 끝날 때 이슈 발생
// 1. 정상 케이스
Member refMember = em.getReference(Member.class, member1.getId()); 
refMember.getUsername(); // 이 시점에서 실제 DB 조회 및 출력

// 2. 준영속 상태
Member refMember = em.getReference(Member.class, member1.getId()); 
em.detach(refMember); //또는 em.close() 또는 em.clear() 해도 같은 예외 발생
refMember.getUsername(); // Exception 발생
// 하이버네이트 예외발생 LazyInitializationException - No session

 

프록시 확인

1. 프록시 인스턴스의 초기화 여부 확인

  • entityManagerFactory.getPersistenceUnitUtil().isLoaded(Object entity);

2. 프록시 클래스의 확인 방법

  • entity.getClass().getName();

3. 프록시 강제 초기화 (비표준)

  • org.hibernate.Hibernate.initialize(entity);
  • 참고 : JPA 표준은 강제 초기화 없으므로, 객체 조회로 강제 호출(member.getName();)해야함

 


 

 

 

JPA기본편 스터디 함께한 Jarry, Matt 항상 감사합니다.

 

 

참고 :

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com