[JPA] 프록시

hi·2022년 10월 1일

다음과 같은 상황이 있다

  • Member와 Team이 연관관계를 가짐
  • Member 객체만 필요한 상황
  • Team까지 메모리에 올라오면 리소스의 낭비

💡 이때, 지연로딩을 사용한다면 ?

  • Team을 지연로딩으로 처리
  • Team을 제외한 Member의 상태만으로 로직 처리
  • 리소스의 낭비를 줄임

이러한 지연로딩이라는 기술을 가능하게 하는 것이 프록시이다


em.getReference()

em.find() 데이터베이스를 통하여 실제 엔티티 객체를 조회
em.getReference() 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

Member findMember = em.getReference(Member.class, member.getId()); //프록시 조회

System.out.prinln("findMember.id = " + findMember.getId()); //쿼리X
System.out.prinln("findMember.username = " + findMember.getUsername()); //쿼리O
  • 이미 프록시를 Member.getId()로 조회하였으므로 findMember.getId() 에서는 쿼리 X
    findMember.getUsername() 는 DB에서 값을 가져와야 하므로 쿼리 O

프록시는

  • 실제 클래스를 상속 받아 생성
  • 실제 클래스와 겉 모양 동일
  • 사용하는 입장에서는 진짜 객체 or 프록시 객체 구분하지 않고 사용 (이론상)
  • 프록시 객체는 실제 객체의 참조 (target)을 보관
  • 프록시 객체를 호출시, 프록시 객체는 실제 객체의 메소드 호출

프록시 객체 초기화

Member member = em.getReference(Member.class, “id1”); //프록시 객체
member.getName(); //호출

순서

프록시 호출 (처음엔 target 값이 없음)
-> 영속성 컨텍스트에 값 요청 (초기화 요청)
-> DB를 조회하여 실제 Entity 생성
-> target과 Entity 연결


  • 처음 한 번만 초기화

  • 초기화 시, 프록시 객체가 실제 엔티티로 바뀌는 것이 아님
    초기화되면 프록시 객체를 통해 실제 엔티티로 접근

  • 프록시 객체는 원본 엔티티를 상속
    👉 JPA Entity 타입 비교시 == 가 아닌 instance of 사용

  • JPA는 같은 영속성 컨텍스트 안에서 == (참조값) 비교시 true를 보장 (동일성 보장)
    👉 찾는 엔티티가 이미 영속성 컨텍스트에 있으면 em.getReference() 를 호출해도 실제 엔티티를 반환
    👉 한 번 em.getReference()로 조회하면 이후 em.find()로 조회해도 프록시 객체 반환

  • 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (hibernate : LazyInitializationException)
    ex) em.clear , em.detach, em.close 후 초기화


프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    PersistenceUnitUtil.isLoaded(Object entity)
emf.getPersistenceUnitUtil().isLoaded()
  • 프록시 클래스 확인 방법
    entity.getClass.getName()

  • 프록시 강제 초기화
    Hibernate.initailize(entity)
    Hibernate 제공

  • JPA 표준은 강제 초기화가 없음
    강제 호출 : member.getName()


지연 로딩 LAZY

@ManyToOne(fetch = FetchType.LAZY)

  • 연관관계 객체를 프록시로 조회
  • 객체의 요소를 실제 사용할 때 초기화 (DB 조회)

즉시 로딩 EAGER

@ManyToOne(fetch = FetchType.EAGER)

  • JOIN을 사용하여 한번에 함께 조회
  • 프록시가 아닌 진짜 객체

주의

  • 실무에서는 가급적 지연 로딩만 사용
  • 즉시 로딩 사용시 예상하지 못한 SQL 발생
  • 즉시 로딩은 JPQL에서 N+1 문제 발생
    1은 최초 쿼리 , N은 추가 쿼리
  • @ManyToOne, @OneToOne 기본값 즉시 로딩
    👉 LAZY로 설정

  • @OneToMany, @ManyToMany 기본값 지연 로딩


활용 (이론상)

Member , Team 은 자주 함께 사용 👉 즉시 로딩
Member , Order 는 가끔 사용 👉 지연 로딩
Order, Product 는 자주 함께 사용 👉 즉시 로딩

결론!

  • 실무에서는 모든 연관 관계에 지연 로딩 사용
  • JPQL fetch 조인, 엔티티 그래프 기능 사용

0개의 댓글