프록시

dereck·2025년 2월 21일

JPA

목록 보기
10/13
post-thumbnail

김영한님의 JPA 강의를 보고 정리한 내용입니다.

프록시

프록시는 다음과 같은 질문에서 시작한다.

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

질문에 대한 해답은 "실제 필요한 비즈니스 로직에 따라 다르다"이다. 비즈니스 로직에서 필요하지 않을 때도 있는데, 만약 항상 Team을 같이 조회한다면 이는 낭비일 것이다.

JPA는 위와 같은 행동(낭비)를 방지하기 위해 지연 로딩프록시로 해결한다.

프록시 기초

뒤에 나올 지연 로딩을 이해하려면 프록시의 개념을 완벽히 이해해야 한다.

프록시는 쉽게 말해 실제 객체를 상속한 가짜 클래스이다. 프록시 객체는 실제 클래스와 겉 모양이 같고, 클라이언트의 입장에선 내가 호출한 것이 프록시 객체인지 실제 객체인지 알 수 없다.

또한 프록시 객체는 실제 객체의 참조(target)를 보관한다. 프록시 객체의 메서드를 호출하면 프록시 객체는 실제 객체(target)의 메서드를 호출한다.

JPA에는 em.find() 말고 em.getReference() 메서드도 제공한다.

  • em.find(): DB를 통해 실제 엔티티 객체를 조회하는 메서드
  • em.getReference(): 프록시 엔티티 객체를 조회하는 메서드

em.find()로 객체를 조회하면 데이터베이스에 바로 쿼리가 나간다.

  • 지금 당장 필요하지 않아도 바로 쿼리 나감
    하지만 em.getReference()로 객체를 조회하면 실제 객체가 사용되는 순간에 DB로 쿼리가 나가게 된다.
  • 진짜 필요한 순간에 쿼리 나감
    • 실제 객체가 사용되는 순간 == 실제로 필요한 시점

프록시 객체의 초기화 과정

상황: em.getReference()Member 프록시 객체를 조회한 뒤에 member.getName()으로 강제 호출 시 내부에선 어떻게 동작하는지?

  1. em.getReference()로 프록시 객체를 조회한 뒤에 member.getName()으로 강제 호출을 하면
  2. Member 프록시 객체의 target 값이 처음에 없기 때문에 JPA가 영속성 컨텍스트에 초기화를 요청한다.
  3. 영속성 컨텍스트는 실제 DB를 조회한 뒤에
  4. 실제 엔티티 객체를 생성하고
  5. 프록시 객체의 target과 연결시켜준다.
    • 프록시 내부에는 Member target 이라는 멤버 변수가 있다고 생각
  6. 그래서 getName()을 했을 때 프록시 객체 내부의 target.getName()을 통해 Member에 있는 member.getName()이 반환이 됨.

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화 된다.
  • 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해진다.
  • 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크 시 주의해야 한다.
    - == 비교 대신 instance of 사용
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티가 반환된다.
    - 1차 캐시에 이미 올라가 있기 때문에 굳이 프록시를 거칠 필요가 없음
    - JPA는 한 트랜잭션 내에서 같은 식별자에 대한 객체 조회는 동일성을 보장
    - 반대의 경우에도 동일하게 작동한다.
    - 프록시 먼저 호출 후 DB를 통해 조회하는 em.find() 사용해도 select 쿼리는 나가지만 프록시 반환됨 -> 동일성 보장을 위함
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.
    - 상당히 자주 만나는 문제
    - org.hibernate.LazyInitializationException 예외를 터트림
    - em.detach(), em.clear(), em.close() 모두 같은 에러 발생

프록시 확인

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

  • emf.getPersistenceUnitUtil().isLoaded(Object entity)

프록시 클래스 확인 방법

  • entity.getClass().getName()

프록시 강제 초기화 (하이버네이트에서 제공)

  • org.hibernate.Hibernate.initialize(entity)

JPA 표준 상 강제 초기화는 없기 때문에 강제 호출을 해야 한다.

강제 호출(실제 객체의 메서드를 사용해서 초기화): entity.getXxx()

References

0개의 댓글