스프링 공부/JPA

[JPA] 16. 프록시? Proxy? FrogC? 개구리씨?

장아장 2022. 12. 1. 23:22

한짤 요약(이 아닙니다)

 

프록시를 공부하기 이전에 이전에 영속성 컨텍스트에서 엔티티를 가져와 사용하거나, DB에서 데이터를 가져와 객체로 사용했던 

이전의 과정을 기억해보았다. 

 

왜?

프록시는 이 부분에서 엔티티를 사용할 때 마다 데이터를 불러오고, 영속화시키고, 영속성 컨텍스트에서 찾아오고를 줄여주기 위한 녀석이다. 

 

Proxy, 영어로 '대리인' 이라는 뜻이 있다. 

말 그대로 엔티티에 대한 대리인의 역할을 한다. 

 

대리인, 혹은 복제품, 혹은 가짜라고 다른 글들에서 언급이 되어있다. 그 말대로, 프록시는 원본 객체가 아닌 원본에 대한 복제품을 가져오는 것이다. 

 

가져오는 방법은, EntityManager.find()를 사용하는 것이 아닌, EntityManager.getReference()를 사용하는 것이다. 

이 때 1차 캐시에 찾아오려는 객체가 없는 경우, 프록시 객체를 조회한다. 

 

이렇게 임시로 객체를 불러오고, 데이터가 필요할 때 쿼리문이 나가며 데이터를 가져온다. 

 

예를 들어 아이디가 1인 Member를 찾으려 할 때, getReference를 사용했다고 생각해보자. 

이 때 class이름은 Member의 프록시, id = 1이다. 

실제로 잘 나온다. 

 

이후에 멤버의 인스턴스를 불러와 사용할 때 쿼리문으로 select해온다. 

 

프록시는 원본 객체의 '대리' 혹은 '가짜'이다. 원본과 구조는 같지만, 속은 비어있는 상태이다. 

여기에 다른 인스턴스가 하나 더 존재하는데,

Entity target이다. 어느 엔티티의 프록시인지가 정해져 있지 않기 때문에 초기엔 null로 되어있다. 

 

이 프록시는 실제 클래스를 상속받아 만들어진다. 겉 모양은 동일해지고, Entity target은 초기화된다. 

이후 동작되는 방식을 정리해보자면, 

  1. 클라이언트가 프록시에게 getName을 요청한다. 
  2. 프록시 객체가 영속성 컨텍스트에게 엔티티 초기화를 요청한다. 
  3. 영속성 컨텍스트가 DB에서 데이터를 조회한다. 
  4. 데이터를 받아온 영속성 컨텍스트가 원본 엔티티를 만든다. 
  5. 생성한 엔티티에서 getName메서드를 호출에 데이터를 반환받는다.

 

 

 

이렇게 동작되는 프록시에도 몇가지 특징이 존재한다. 

  1. 프록시 객체는 처음 사용할 때 한번만 초기화된다.
    같은 동작을 몇번 반복해도 초기화되지 않는다. 
  2. 프록시를 초기화해도 실제 엔티티를 바꾸는 것이 아니다.
    접근만 해오는 것이다. 결국 데이터를 수정할 때에는 프록시가 아닌 원본 객체를 가져온 상태여야 한다. 
  3. 프록시 객체와 원본 객체를 .eqauls하면 다르게 나온다. class가 다르기 때문이다.
    만약 프록시 객체의 인스턴스와 원본 인스턴스를 비교하려면 인스턴스를 불러와 비교를 하거나
    Instance of로 클래스 속성을 비교하는게 맞다. 
  4. 영속성 컨텍스트에 해당 엔티티가 있다면, 프록시가 아닌 원본 객체를 불러온다. 
  5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태에서 프록시를 초기화하면 예외가 발생될 수 있다. 
    getReference를 이용하면 프록시로 객체를 불러온다. 이 객체는 영속성 컨텍스트에 존재하는데, 영속성 컨텍스트에서 해당 데이터를 제거하고 다시 호출할 경우, LazyInitializationException이 발생한다. 
    영속성 컨텍스트에서 프록시를 초기화해야하는데, 영속성 컨텍스트에서 프록시가 관리되지 못한다는 뜻이다. 

 

4번에 대한 부연설명을 조금 더 붙이자면, 

이 상태에서는 Proxy가 아닌 원본 객체가 와있다.

프록시를 한번 호출 한 이후에, em.find로 다시 데이터를 찾으려 해도 프록시를 가져와 반환시킨다.
이렇게 되었을 때 문제는, 프록시를 수정해도 원본 객체가 수정되지 않기 때문에, 꼬일 위험이 있다.
즉, 프록시가 먼저 호출되었으면 프록시로만 오게되고, 원본 객체를 불러오면 원본 객체만 불러온다. 

 

프록시 사용을 원할하게 해주는 메서드들도 존재한다. 

EntityManagerFactory.
getPersistenceUnitUtil().isLoaded(Object entity)
프록시 인스턴스의 초기화 여부 확인. 즉 인스턴스가 영속성 컨텍스트로 인해 초기화 되었는지 확인해주는 역할을 한다. 
entity.getClass().getName() 프록시 클래스를 확인한다.
Hibernate.initialize(entity) 프록시를 강제로 초기화시켜준다. 이는 하이버네이트 기준이며
JPA 정형화된 강제 초기화법이 존재하지는 않는다. 

 

사실 이 프록시를 배우는 이유는 두가지 이다. 

첫 번째로는 엔티티를 사용하는 매 순간 원본 객체를 가져오면 DB에 쿼리 이동량이 늘어나며, 최적화가 되지 않을 수 있다. 

필요할 때가 되면 데이터를 가져오는 식으로 조정할 수 있게 된다. 

두 번째로는 이렇게 데이터를 프록시로 불러오고, 필요할 때 원본 객체를 가져오는 식으로 지연로딩을 할 수 있다. 즉시로딩보다 효율적인 부분도 있다.

사실 이렇게 말해도 이해하기 어렵기도 하니, 즉시로딩과 지연로딩을 빨리 공부해야겠다.