본문 바로가기

JPA/Proxy

Proxy 주의사항

2021.08.22 - [JPA/Proxy] - Proxy란?

 

Proxy란?

전체코드 github 주소(branch: blog/proxy) JPA에서 Proxy(프록시)란 무엇일까? 한 번 살펴보자!!! getReference EntityManager에서 제공하는 메서드 중 `getReference`라는 메서드가 있다. `getReference` 메서드..

code-mania.tistory.com

저번 글에서 Proxy란 무엇인지 알아봤다!!
이번 글에서는 Proxy를 사용할 때 주의해야 할 점에 대해 알아보자!

 


github 전체코드 주소(branch: proxyPrecautions)

Proxy와 타입체크

JpaMain.java - isStudent 메서드

public static boolean isStudent(Object student) {
  // ==으로 타입 체크
  System.out.println("refStudent = " + (student.getClass() == Student.class));
  // instanceof로 타입체크
  System.out.println("refStudent instanceof Student = " + (student instanceof Student));
  return student instanceof Student;
}

(코드는 부분부분 살펴보겠습니다! 전체코드는 상단의 github에서 확인가능합니다!)
JPA를 사용할 때 Entity의 객체가 어떤 클래스의 객체인지 판별해야 한다면, ==을 통해서 비교하는 것은 금지다.
반드시 instanceof를 통해서 클래스 타입을 확인해야 한다.

이는 Proxy 객체때문이다.
위 코드에서 인자로 넘어온 student는 Student class를 상속받은 Proxy class의 객체인지, Student class의 객체인지 알 수 없다.
만약 student가 Proxy 클래스의 객체라면, refStudent.getClass() 결과값으로 proxy class가 반환되고,
student.getClass() == Student.class의 결과값은 false가 나오게 된다.
반면에 student instanceof Student의 결과값은 Proxy 객체가 넘어와도 true가 나온다. 
Proxy class가 원본 엔티티를 상속받아 만들어지기때문에 instanceof의 결과로 true가 나오는 것이다.
(당연히 Proxy class가 상속받지 않은 다른 class와 instanceof 연산을 수행하면 false가 나온다.)

지금처럼 JPA에서 객체의 타입을 체크할 때 객체의 클래스가,
Entity인지 Proxy인지 알 수 없는 상황에서는 반드시 instanceof로 타입을 체크해야 한다.
(설령 타입을 체크할 객체의 class가 Entity인지 Proxy인지 판단할 수 있다고 해도 instanceof를 추천한다!)

Proxy와 동일성 보장

JPA는 동일한 트랜잭션 내에서 조회하는 id가 같은 엔티티에 관해서는 동일성을 보장해준다.
물론 Proxy를 사용할 때도 마찬가지이다.
코드를 통해 Proxy를 사용할 때 동일성 보장이 어떻게 이뤄지는지 살펴보자!

 

JpaMain.java - checkIdentity 메서드

    public static void checkIdentity(EntityManager em, Student student) {
        Student refStudent = em.getReference(Student.class, student.getId());
        Student findStudent = em.find(Student.class, student.getId());

        System.out.println("refStudent = " + refStudent.getClass());
        System.out.println("findStudent = " + findStudent.getClass());
        System.out.println("(refStudent == findStudent) = " + (refStudent == findStudent));
        
        em.flush();
        em.clear();

        Student findStudent2 = em.find(Student.class, student.getId());
        Student refStudent2 = em.getReference(Student.class, student.getId());

        System.out.println("refStudent2 = " + refStudent2.getClass());
        System.out.println("findStudent2 = " + findStudent2.getClass());
        System.out.println("(refStudent2 == findStudent2) = " + (refStudent2 == findStudent2));
    }

em.flush()의 위쪽과 아래쪽 코드가 거의 동일한 것을 알 수 있다.
차이점은 위쪽에서는 getReference()를 호출 후 find()를 호출했고,
아래쪽에서는 반대로 find()를 호출 후 getReference()를 호출했다.

 

1. getReference()를 먼저 호출하는 경우
getReference()에 의해 Proxy class의 객체가 영속성 컨텍스트에 저장된다.
그 후 find()를 통해 student를 조회할 때에는 영속성 컨텍스트에 저장된 Proxy class의 객체를 꺼내오게 된다.
refStudent와 findStudent의 getClass()가 동일하게 proxy class를 반환하며, ==도 당연히 true가 나온다.

 

2. find()를 먼저 호출하는 경우
find()에 의해 Entity class의 객체가 영속성 컨텍스트에 저장된다.
그 후 getReference()를 통해 student를 조회할 때에는 영속성 컨텍스트에 저장된 Entity class의 객체를 꺼내오게 된다.
refStudent와 findStudent의 getClass()가 동일하게 Entity class를 반환하며, ==도 당연히 true가 나온다.

 

결과적으로 동일성 보장에 의해 둘 다 비교결과로 true가 찍힌다.

 

Proxy 관리

JpaMain.java - errorCheck 메서드

errorCheck(EntityManager em, Student student) {
    try {
        Student refStudent = em.getReference(Student.class, student.getId());
        // refStudent 준영속화
        em.detach(refStudent);
        // refStudent 초기화
        refStudent.getName();
    } catch (LazyInitializationException e) {
        System.out.println("0. error====================================");
        e.printStackTrace();
    }
}

 

errorCheck log

영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화하면 문제가 발생한다.
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터뜨린다)

 

JpaMain.java - proxyUtilll 메서드

public static void proxyUtill(EntityManagerFactory emf, EntityManager em, Student student) {
    System.out.println("1. proxy utill====================================");
    Student refStudent = em.getReference(Student.class, student.getId());
    // proxy class 이름 확인
    System.out.println("refStudent.getClass().getName() = " + refStudent.getClass().getName());
    // proxy class 초기화 여부 확인
    System.out.println("emf.getPersistenceUnitUtil().isLoaded(refStudent) = " + emf.getPersistenceUnitUtil().isLoaded(refStudent));
    // proxy class 초기화
    Hibernate.initialize(refStudent);
    System.out.println("emf.getPersistenceUnitUtil().isLoaded(refStudent) = " + emf.getPersistenceUnitUtil().isLoaded(refStudent));
}

proxyUtill log

  • 프록시 클래스 확인: entity.getClass().getName() 출력
  • 프록시 강제 초기화: Hibernate.initialize(findMember);
    (하이버네이트에서는 Hibernate.initialize()를 통해 지원, JPA 구현체에 따라 없을 수도 있음)
  • 프록시 인스턴스의 초기화 여부 확인: emf.getPersistenceUnitUtil().isLoaded(findMember)

위 코드에서 처음에 호출한 isLoaded()는 false를 반환했지만,
Hibernate.initialize() 호출 후에 다시 호출한 isLoaded()는 true를 반환한 것을 볼 수 있다.

 

참고강의: 배달의 민족 개발팀장 김영한 강사님의 JPA 강의

'JPA > Proxy' 카테고리의 다른 글

즉시로딩과 지연로딩  (0) 2021.08.23
Proxy란?  (0) 2021.08.22