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)
를 지원- 하위 타입의 필드를 직접 사용할 때 조회 쿼리가 나가게 됨
- 지연로딩 LAZY을 사용해 프록시로 조회
- Member 랑 Team을 거의 항상 같이 사용한다면??
- 즉시로딩 EAGER를 사용해서 함께 조회
@<N>To<M>(fetch = FetchType.EAGER)
- 즉시로딩 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의 개념을 구현할 때 유용