[JPA] 8. 프록시와 연관관계 관리

지니🧸·2023년 2월 15일
0

Spring Boot & JPA

목록 보기
15/35

본 문서는 인프런의 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한) 강의를 공부하며 작성한 개인 노트입니다.

🎻 프록시

em.find() - 디비를 통해 실제 엔티티 객체 조회
em.getReference() - 디비 조회를 미루는 가짜(프록시) 엔티티 객체 조회

  • getReference을 호출할 때는 디비 쿼리를 안함
  • 반환 타입: HibernateProxy

프록시란?

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉모양이 같음
  • (이론상) 사용하는 입장에서는 진짜/프록시 객체 구분없이 사용하면 됨
  • 프록시 객체는 실제 객체의 참조(target)을 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

프록시에 실제 객체 메소드 호출 과정

  1. 클라이언트가 프록시에 실제 객체 메소드 호출함
  2. 아직 타겟 값이 없는 프록시 객체는 영속성 컨텍스트에 초기화 요청
  3. 영속성 컨텍스트가 디비를 조회해서 실제 엔티티를 생성하여 프록시 타겟으로 연결해줌
  4. 이후로 메소드 호출하면 프록시가 알아서 객체 메소드 호출

프록시 특징

  • 프록시 객체는 처음 사용할 때 한번만 초기화
  • 프록시 객체를 초기화할때 프록시 객체가 실제 엔티티로 바뀌는 것이 아님.
    • 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음 > 타입 체크시 주의해야함
    • ==는 X.
    • instanceof 사용.
    • (예) System.out.println("m1 == m2" + (m1 instanceof Member));
    • 프록시와 실제 엔티티는 == 비교시 true
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
    • 프록시를 조회 후에 실제 엔티티를 부르면 (em.find) 프록시 객체를 반환한다
      • == 비교시 true여야 하기 때문
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    • 준영속 상태로 만드는 방법: em.detach(member), em.close()
    • hibernate.LazyInitializationException 예외 발생 > 영속성 컨텍스트가 없다 등

프록시 확인

프록시 인스턴스의 초기화 여부 확인 - PersistenceUnitUtil.isLoaded(Object entity)

System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember);

프록시 클래스 확인 방법 - entity.getClass().getClass() 출력 > javasist or HibernateProxy

System.out.println("refMember = " + refMember.getClass());

프록시 강제 초기화 - org.hibernate.Hibernate.initialize(entity)

Hibernate.intialize(refMember);
  • 참고: JPA 표준은 강제 초기화 없음. 강제 호출 > member.getName()

🪕 즉시 로딩과 지연 로딩

지연 로딩 LAZY

지연로딩(LAZY)으로 프록시 조회
(예) Member 객체를 조회할 때마다 Team까지 조회할 필요는 없음 > team을 LAZY 설정

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;
  • member.getTeam() 등 경우에 team을 실제로 사용하는 시점에 team 초기화

즉시 로딩 EAGER

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
  • proxy 없이 실제 객체를 무조건 불러옴
  • Member 조회시 항상 Team도 조회
  • JPA구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회

프록시와 즉시로딩 주의

  • 실무에서는 가급적 지연로딩만 사용
  • 즉시 로딩 적용시 예상치 못한 SQL 발생
    • JPQL에서 N+1 문제 발생
  • @ManyToOne, @OneToOne은 디폴트가 즉시 로딩 > LAZY로 설정
  • @OneToMany, @ManyToMany는 디폴트가 지연 로딩

활용

자주 함께 사용 > 즉시로딩
가끔 함께 사용 > 지연로딩

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

CASCADE: 특정 엔티티를 영속 상태로 만들때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때
(예) 부모 엔티티 저장시 자식 엔티티도 함께 저장

@Entity
public class Parent {
	...
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childList = new ArrayList<>();
    ...
}

주의

  • 영속성 전이는 연관관계 매핑과 관련 없음
  • 엔티티를 영속화할 때 연관 엔티티도 함께 영속화하는 편리 기능 제공

CASCADE 종류

  • ALL: 모두 적용
  • PERSIST: 영속
  • REMOVE: 삭제
  • MERGE: 병합
  • REFRESH: refresh
  • DETACH: detach

고아 객체

고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동삭제

  • orphanRemoval = true
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();

주의사항

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일때 사용
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능
  • 개념적으로 부모를 제거하면 자식은 고아가 됨. > 고아 객체 제거 기능 활성화 시 부모 제거하면 자식도 함께 제거됨 > CascadeType.REMOVE와 동일한 효과

영속성 전이 + 고아 객체, 생명주기

  • 스스로 생명주기 관리하면 em.persist(), em.remove() 사용
  • CascadeType.ALL + orphanRemoval=true > 부모 엔티티를 통해서 자신의 생명 주기 관리 가능
  • 도메인 주도 설계(DDD)의 Aggregate Root 개념 구현시 유용

🎸 실전 예제 5 - 연관관계 관리

모든 연관관계는 지연 로딩으로

profile
우당탕탕

0개의 댓글