JPA

프록시와 연관관계 관리

salmon16 2023. 8. 30. 17:31

Member와 Team이 연관관계로 매핑이 되어있는 상황을 예시로 생각해 보자

비즈니스 로직에 따라 Member를 조회하고 자주 Team을 찾는 경우, Member만 사용을 많이 하는 경우가 있다.

각 경우에 따라 전자는 Team까지 같이 가져오는 SQL을 하는 것이 후자는 Member만 가져오는 SQL을 작성하는 것이 유리하다. 

 

이를 해결하지위해 JPA는 지연로딩과 프록시를 사용한다.

프록시

em.find() vs em.getReference()

  • em.find : 데이터베이스를 통해서 실제 엔티티 객체를 조회 
    • Member와 Team 테이블을 Join해서 가져온다
  • em.getReference() : 데이터베이스 조회를 미루는 가짜(프락시) 엔티티 객체 조회
    • getReference하는 시점에는 DB에 쿼리가 나가지 않는다.
    • getReference한 값이 사용되는 시점에 DB에 쿼리를 보낸다.
    • getReference를 해서 얻은 인스턴스의 class를 조회하면 프록시 클래스로 나온다.

getReference()를 하면 하이버네이트가 라이브러리로 Proxy 엔티티 객체를 준다.

target이 진짜 객체를 가리킨다. 

 

프록시의 특징

  • 실제 클래스를 상속받아서 만들어진다.
  • 실제 클래스와 겉모양이 같다
  • 사용하는 입장에서 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
  • 프록시 객체는 실제 객체의 참조(target)을 보관한다.
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
Member member = em.getReference(Member.class, "id");
member.getName();

위 코드가 수행되는 과정을 살펴보자

  • getReference를 하면 프록시 객체를 얻는다.
  • getName()을 호출하면  초기에 프록시의 target의 값이 비어있으므로 JPA가 영속성 컨텍스트에 초기화 요청을 한다.
  • 영속성 컨텍스트가 DB에서 조회를 해서 실제 Entity객체를 생성해서 준다.
  • target과 실제 Entity 객체를 연결시켜준다.
  • target의 진짜 getName()을 통해서 Member의 Name이 반환된다.
  • 이후에 요청은 초기화가 된 상태이므로 초기화가 더 이상 필요 없다.

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화하면 된다.
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 초기화가 되면 프록시 객체를 통해 실제 엔티티에 접근이 가능하다.
  • 프록시 객체는 원본 엔티티를 상속받는다, 따라서 타입 체크 시 주의해야 한다. (==비교 대신, instance of사용 )
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
    • case1 
    • Member member = em.find(Member.Class, "1");
    • Member reference = em.getReference(Member.Class, "1");
    • member와 reference의 class타입이 동일하다. 즉 reference는 프록시가 아니라 실제 엔티티이다. 
    • case2
    • JPA는 한 트렌잭션 안에서 같은 Id로 조회한 두 인스턴스는 ==을 보장해 준다.
    • Member refMember = em.getReference(Member.Class, "1");
    • Member findMember = em.find(Member.Class, "1");
    • 두 코드가 순서대로 한 트렌잭션 안에서 실행되었으면 JPA는 ==을 보장해 주기 위해 findMember도 Proxy타입으로 반환된다.
  • 영속성 컨텍스트에 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화 하면 문제가 발생한다.
    • Member refMember = em.getReference(Member.Class, "1");
    • em.clear() , em.detach(refMember), em.close()
    • refMember.getName();
    • could not initialize proxy예외가 발생한다.

프록시 확인 메서드

  • 프록시 인스턴스의 초기화 여부 확인
    • emf.gerPersistenceUnitUtil().isLoaded(referMember);
  • 프록시 클래스 확인 방법
    • entity.getClass(); 
  • 프록시 강제 초기화 방법
    • Hibernate.initialize(refMember);

 

 

 

 

'JPA' 카테고리의 다른 글

영속성 전이 : CASCADE, 고아객체  (0) 2023.08.31
즉시 로딩과 지연 로딩  (0) 2023.08.30
MappedSuperclass  (0) 2023.08.30
상속관계 매핑  (0) 2023.08.29
다양한 연관관계 매핑  (0) 2023.08.29