본문 바로가기
JPA

JPA - 프록시

by 왈레 2022. 4. 11.

Member를 조회할 때 Team도 함께 조회해야 할까?

  • Member와 Team이 연관관계를 가짐
  • Member 객체만 필요한 상황
  • Team까지 메모리에 올라오면 리소스의 낭비

이때 지연로딩을 사용한다면?

  • Team을 지연로딩으로 처리
  • Team을 제외한 Member의 상태만으로 로직 처리
  • 리소스의 낭비를 줄임

지연로딩을 이해하기 위해 먼저 프록시를 이해해보자

 

프록시 기초

• 뒤에 나올 지연로딩이라는 기술을 가능하게 하는 것이 프록시이다

• em.find() vs em.getReference()

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

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

 

 

프록시 특징(1)

• 실제 클래스를 상속 받아서 만들어짐

• 실제 클래스와 겉 모양이 같다.

이론상 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.

 

 

프록시 특징(2)

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

• 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

 

프록시 객체의 초기화

Member member = em.getReference(Member.class, “id1”); 
member.getName();

 

프록시의 특징(3)

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서
    실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
    (JPA의 효율성, 객체의 동일성 보장)
  • 처음 em.getReference()로 프록시를 호출하면 이후에 em.find()를 호출해도 프록시가 반환 (객체의 동일성 보장)
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화(영속성 컨텍스트 초기화)하면 문제 발생
    (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

 

em.getReference()

Member findMember = em.getReference(Member.class, member.getId()); //프록시 조회

System.out.prinln("findMember.id = " + findMember.getId()); //쿼리X
System.out.prinln("findMember.username = " + findMember.getUsername()); //쿼리O

 

※ 객체의 동일성 보장이란?

영속성 컨텍스트는 영속 상태의 엔티티들에 대한 동일성(Identity)을 보장한다.

1차 캐시에 의해, member 객체 하나를 두 번 조회해도 이 둘은 같은 객체(레퍼런스)가 된다.

애플리케이션 차원에서 1차 캐시에게 repeatable read인 트랜잭션 격리 수준을 제공한다.

(트랜잭션 격리수준은 크게 네 가지가 존재한다. 그중 하나가 repeatable read이다.)

 

※ repeatable read란?

같은 트랜잭션에서 같은 질의를 사용했을 때 질의를 아무리 여러번 해도 그리고 다른 트랜잭션에서 아무리 여러 번 그 행을 변경해도 항상 같은 데이터만 읽어드리는 경우를 Repeatable read라고 합니다.

 

 

프록시 확인

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

PersistenceUnitUtil.isLoaded(Object entity)

 

프록시 클래스 확인 방법

entity.getClass().getName() // 출력(..javasist.. or HibernateProxy…)

 

프록시 강제 초기화

org.hibernate.Hibernate.initialize(entity);

 

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

member.getName()

 

 

 

※ "instanceof" 와 "==" 차이

instanceof 연산자는 상속 관계에 있는 객체를 두고 자식 여부를 확인하는 것이고, 

== 연산자는 서로 다른 객체의 주소값의 일치 여부를 확인할 때 사용됩니다.

 

※ get.class()란?

자바(Java)에서는 Object 클래스에 있는 getClass()를 통해 여러가지 클래스에 대한 메타 속성 정보를 얻을 수 있습니다.이 처럼 객체를 통해 클래스의 정보를 분석하는 것을 리플렉션(reflection - java.lang.reflect)이라 하고 주로 클래스를 동적으로 로딩하거나 디컴파일(컴파일된 소스를 다시 반대로 되돌리는 일련의 과정) 할 때 많이 활용됩니다.

 

TestClass testClass = new TestClass();

System.out.println("getName() : " + testClass.getClass().getName());
System.out.println("getSimpleName() : " + testClass.getClass().getSimpleName());

// getFields()
for(Field field : testClass.getClass().getFields()){ 
    System.out.println("field: " + field.getName()); //testClass의 필드를 출력
}

// getMethods()
for(Method method : testClass.getClass().getMethods()){
    System.out.println("method: " + method.getName()); //testClass의 메서드를 출력
}

 

getName()  - 각 패키지명이 포함된 클래스명

getSimpleName() - 패키지 경로가 포함되지 않은 클래스명

getFields() - 클래스의  필드 목록을 뽑아냄

getMethods() - 클래스의 메서드 목록을 뽑아냄

댓글