BE/JPA

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

멍목 2022. 4. 21. 00:17
반응형

자바 ORM 표준 JPA 프로그래밍

 

 

 

 

이 포스팅에서 작성하는 내용은 자바 ORM 표준 JPA 프로그래밍 (김영한 지음) 에서 발췌하였습니다.

 

 

 

1. 프록시

 DB 조회를 미루는 가짜 엔티티. (실제로 객체가 사용될 때. 그 때 조회함)

 

1) 프록시 기초

  • em.find() vs em.getReference()
    • em.find() : DB를 통해서 실제 엔티티 객체 조회
    • em.getReference() : DB 조회를 미루는 가짜(프록시) 엔티티 (실제로 객체가 사용될 때. 그 때 조회함)

 

2) 프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지않고 사용하면 됨
  • 프록시 객체는 실제 객체의 참조를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체는 원본 엔티티를 상속받음. 따라서 타입 체크 시 주의(==이 아닌, instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 존재하면 em.getReference()를 호출해도 실제 엔티티를 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생

 

3) 프록시 객체의 초기화

프록시 객체를 초기화 시, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근하는 방식

  1. 객체가 실제로 사용됨 ex)member.getName()
  2. 실제 객체의 참조를 보관하던 프록시 객체가 영속성 컨텍스트에 데이터를 요청
  3. 영속성 컨텍스트에서 DB로 값을 조회함
  4. 실제로 해당 객체를 생성한 뒤 DB에서 얻은 값을 넣음

 

4) 프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    • PersistenceUnitUtil.isLoaded(Object entity)
    • 초기화 X : false
    • 초기화 O : true
  • 프록시 클래스 확인
    • entity.getClass().getName() 출력 (..javasist.. or HibernateProxy...)
  • 프록시 강제 초기화
    • org.hibernate.Hibernate.initialize(entity);
    • JPA 표준에는 강제 초기화가 없다.

 

 

2. 즉시로딩과 지연로딩

즉시로딩 : 관련된 엔티티를 한 번에 같이 조회

지연로딩 : 관련된 엔티티는 나중에 조회(실제로 사용될 때 조회)

 

 

1) 프록시와 즉시로딩 주의

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다. (즉시 로딩 해야하는 관련 엔티티를 조회하기 위해 추가 쿼리가 나감)
  • @ManyToOne, @OneToOne의 Default : 즉시 로딩. → 지연 로딩으로 설정
  • @OneToMany, @ManyToMany의 Default : 지연 로딩 

 

2) 지연 로딩 활용 - 실무

  • 모든 연관관계에 지연 로딩을 사용
  • 실무에서 즉시 로딩 사용 X
  • JPQL fetch 조인이나 엔티티 그래프 기능 사용
  • 즉시 로딩은 복잡하고 상상하지 못한 쿼리 발생

 

 

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

1) 영속성 전이(CASCADE)

  • 특정 엔티티를 영속 상태로 만들 떄 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용
  • ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

 

2) 주의사항

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

 

3) 종류

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

 

 

4. 고아 객체

1) 고아 객체 

고아 객체 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티

  • 고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
Parent p1 = em.find(Parent.class, id);
p1.getChild().remove(0); 	// 자식 엔티티를 컬렉션에서 제거
 	↓ 쿼리
DELETE FROM CHILD WHERE ID = ?;

 

 

2) 주의

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 판단하고 삭제하는 기능임
  • 참조하는 곳이 하나일 때 사용해야함
  • 특정 엔티티가 개인 소유할 떄 사용
  • @OneToOne, @OneToMany일 경우에만 가능
  • 참고) 부모를 제거하면 자식은 고아가 됨 → 부모를 제거하면 자식도 함께 제거된다는 뜻

 

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

  • CascadeType.ALL + orphanRemoval=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화하고 em.remove()로 제거
  • 두 옵션을 모두 활성화하면 부모 엔티티에서 자식의 생명주기 관리 가능
  • 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용
반응형