해당 글은 김영한 님의 자바 ORM 표준 JPA 프로그래밍을 참고하여 작성한 글입니다.

 

✅ 프록시

기능을 구현할 때 DB에서 가져오고 싶은 정보의 범위가 비즈니스 로직에 따라 다르다. 예를 들어, Member와 Team이라는 두 개의 entity가 연관관계상에 있을 때, 구현 목적에 따라 두 개의 데이터가 한 번에 조회되는 것이 좋을 수도 있고, 그렇지 않을 수도 있다. 이러한 상황을 JPA가 프록시와 지연로딩을 통해 해결한다. 이 글에서는 우선 프록시에 대해 알아보겠다.

 

▶ 프록시 기초

1️⃣ em.find()

  • DB를 통해 실제 Entity 객체를 조회하는 메서드
  • member라는 객체를 사용하지 않고 find()만 해도 SELECT 쿼리를 실행한다.

2️⃣ em.getReference()

  • DB 조회를 미루는 가짜(프록시) Entity 객체를 조회하는 메서드, 이 가짜 객체를 프록시 객체라고 부른다.
  • find()와 반대로 SELECT 쿼리가 실행되지 않는다.
  • 이 메서드를 호출할 때 JPA는 DB를 조회하지 않고 실제 Entity 객체도 생성하지 않는다. 대신 DB 접근을 위임한 프록시 객체를 반환한다.

 

▶ 프록시 특징

  • 프록시 클래스는 실제 클래스를 상속받아서 만들어지므로 실제 클래스와 겉모습이 같다.
  • 프록시를 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용한다.

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

 

▶ 프록시 객체의 초기화

Member member = em.getReference(Member.class, "id1");
member.getName(); //프록시 객체의 초기화

프록시 객체는 member.getName()처럼 실제 사용될 때 DB를 조회해서 실제 Entity 객체를 생성하는데 이를 프록시 객체의 초기화라고 한다. (target에 값이 없는 상태일 때)

  • 프록시의 초기화 과정
    1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
    2. 프록시 객체는 실제 Entity가 생성되어 있지 않으면 영속성 컨텍스트에서 실제 Entity 생성을 요청하는데 이것을 초기화라 한다.
    3. 영속성 컨텍스트는 DB를 조회하여 실제 Entity 객체를 생성한다.
    4. 프록시 객체는 생성된 실제 Entity 객체의 참조를 Member target 멤버변수에 보관한다.
    5. 프록시 객체는 실제 Entity 객체의 getName()을 호출해서 결과를 반환한다.

즉, 위 코드에서 getName()을 호출하는 시점에 영속성 컨텍스트로 Member을 요청하여 실제 참조를 가지게 된다. 이후 다시 getName()을 재요청한다면 영속성 컨텍스트에서 값을 받아왔기 때문에 프록시에서 조회한다.

 

▶ 프록시 주의사항

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
  • 프록시 객체를 초기화한다고 프록시 객체가 실제 Entity로 바뀌는 것은 아니다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해질 뿐이다.
  • 프록시 객체는 원본 Entity를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다. (== 비교가 아닌, instance of 비교 사용)
  • 영속성 컨텍스트에 찾는 Entity가 이미 있으면 DB를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 Entity를 반환한다.
  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트에서 관리하지 않는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.
    • 하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킨다.

 

▶ 프록시 유틸리티 메서드

  • 프록시 인스턴스의 초기화 여부 확인: PersistenceUtilUtil.isLoaded(Object entity)
  • 프록시 클래스 확인 방법: entity.getClass().getName()
  • 프록시 강제 초기화
    • JPA 표준에는 강제 초기화가 없다. member.getName()처럼 프록시의 메서드를 직접 호출해야 한다.
    • 하이버네이트가 제공하는 org.hibernate.Hibernate.initialize(entity)을 통해 가능하다.

 

 

📍 참고

soeun2537