08. 프록시와 연관관계 관리

프록시#

  • 데이터가 꼭 필요할 때가 있고, 지금 당장 필요 없을때가 있어 이를 구분하기 위해 사용되는 전략

기초#

  • em.find(): 바로 조회쿼리가 나가 엔티티 객체가 채워짐
  • em.getReference(): 데이터 베이스 조회를 미루는 가짜 엔티티 객체 조회
    • 처음엔 proxy를 통해 엔티티가 null 로 지정됨

프록시의 특징#

  • 실제 클래스를 상속 받아서 만들어짐 (프록시 라이브러리를 통해 자동 생성)
  • 생제 클래스와 겉 모양이 같음
  • 사용하는 입장에서 진짜 객체인지 프록시 객체인지 구분하여 사용 안해도 됨 (이론상)
  • 중요 포인트
    • 프록시 객체는 처음 사요할 때 한 번만 초기화
    • 프록시 객체 초기화 시, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 실제로 프록시 객체를 통해 실제 엔티티에 접근 가능
    • 프록시 객체는 타입 체크시 주의해야함 (== 대신 instanceof <Class> 사용)
    • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReferece()를 호출해도 실제 엔티티가 있음 (프록시 객체가 아니라 실제 엔티티 바로 가져옴)
      • == 비교에 이점도 있음 (JPA는 동등성 비교를 보장해야되므로)
      • 개발할 때 프록시이든/아니든 신경쓰지 않게 프로그래밍 해야됨
      • 처음에 프록시를 반환했으면 다음 find 를 해도 프록시로 반환함
    • 영속 컨텍스트의 도움을 받을 수 없는 준영속성인 상태일 때 (detach, close, clear 인 상태), 프록시를 초기화하면 문제 발생 (org.hibernate.LazyInitializationException 발생)

프록시 확인#

  • 프록시 인스턴스의 초기화 여부 확인: PersistenceUnitUtil.isLoaded(Object entity)
  • 프록시 클래스 확인 방법: entity.getClass()
  • 프록시 강제 초기화: org.hibernate.Hibernate.initialize(entity)
    • JPA 표준은 강제 초기화 없음 (다른 구현체에 없다면 강제 호출)

즉시 로딩과 지연 로딩#

  • Member를 조회할 때 Team도 함께 조회해야 할까?
    • 지연로딩 LAZY을 사용해 프록시로 조회
      • @<N>To<M>(fetch = FetchType.LAZY) 를 지원
      • 하위 타입의 필드를 직접 사용할 때 조회 쿼리가 나가게 됨
  • Member 랑 Team을 거의 항상 같이 사용한다면??
    • 즉시로딩 EAGER를 사용해서 함께 조회
      • @<N>To<M>(fetch = FetchType.EAGER)
  • 프록시와 즉시로딩 주의 (중요)
    • 가급적 지연 로딩만 사용 (특히 실무에서)
    • 즉시 로딩을 적용하면 에상치 못한 SQL이 발생
    • 즉시 로딩은 JPQL에서 N+1 문제 일으킨다
      • 지연 로딩으로 깔기
        • 그 중 하나의 솔루션 FetchJoin: 런타임으로 선택해서 한꺼번에 가져오기
        • JPQL fetch 조인이나, 연관관계 그래프로 해결 가능
    • @ManyToOne, @OneToOne 은 기본이 즉시 로딩 -> LAZY 로 설정

영속성 전이 (CASCADE) 와 고아 객체#

영속성 전이#

  • 특정 엔티티를 영속 상태를 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
  • 영속성 상태 전이: 저장
    • @OneToMany(mappedBy = ...., cascade = CascadeType.PERSIST)
  • CASCADE 종료
    • ALL: 모두 적용
    • PERSIST: 영속
  • 주의 언제 쓰느냐
    • 하나의 부모가 자식들을 관리할 때 (소유자가 하나일 때)
    • 자식들이 다른데에서도 관리가 될때는 쓰면 안됨

고아 객체#

  • 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
    • orphanRemoval = true
    • child에서 빠진 상태를
  • 주의사항
    • 참조하는 곳이 하나일 때 사용해야함
    • 특정 엔티티가 개인 소유할 때 사용 (소유자가 하나일 때)
    • CascadeType.REMOVAL 처럼 동작

그럼 둘다 키면 무슨 의미#

  • CascadeType.ALL + orphanRemoval = true
  • child의 생명주기를 parent가 관리하게 됨
  • DDD의 Aggregate Root의 개념을 구현할 때 유용
Last updated on