[Spring boot] 프록시와 연관관계 관리

어정윤·2021년 7월 31일
0

Spring boot 스터디

목록 보기
7/15
post-thumbnail

[Spring boot] 프록시와 연관관계 관리

프록시

엔터티를 조회할 때 연관된 엔터티들이 항상 사용되는 것은 아니다.

JPA는 이런 문제를 해결하려고 엔터티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데 이것을 지연 로딩이라고 한다.
그런데 지연 로딩 기능을 사용하려면 실제 엔터티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다.

em.find() vs em.getReference()

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

프록시 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔터티에 접근할 수 있음
  • 프록시 객체는 원본 엔터티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용
  • 영속성 컨텍스트에 찾는 엔터티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔터티를 반환
  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능
    따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제 발생
    (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    PersistenceUnitUtil.isLoaded(Object entity)
  • 프록시 클래스 확인 방법
    entity.getClass().getName() 출력
    -> ..javasist.. or HibernateProxy...
  • 프록시 강제 초기화
    org.hibernate.Hibernate.initialize(entity);

+ JPA 표준은 강제 초기화 없음 -> 강제로 초기화하려면 강제 호출을 해야함(member.getName())

즉시 로딩과 지연 로딩

프록시 객체는 주로 연관된 엔터티를 지연 로딩할 때 사용한다.

JPA는 개발자가 연관된 엔터티의 조회 시점을 선택할 수 있도록 다음 두 가지 방법을 제공한다.

  1. 즉시 로딩 : 엔터티를 조회할 때 연관된 엔터티도 함께 조회. 하이버네이트는 가능하면 SQL 조인을 사용해 한 번에 조회
  • 설정 방법 : @ManyToOne(fetch = FetchType.EAGER)
  1. 지연 로딩 : 연관된 엔터티를 실제 사용할 때 조회
  • 설정 방법 : @ManyToOne(fetch = FetchType.LAZY)

JPA 기본 페치 전략

  • 즉시 로딩 : @ManyToOne, @OneToOne
  • 지연 로딩 : @OneToMany, @ManyToMany

모든 연관관계에 지연 로딩을 사용하고, 꼭 필요한 곳에만 즉시 로딩을 사용하는 것을 추천한다.

영속성 전이: CASCADE

특정 엔터티를 영속 상태로 만들 때 연관된 엔터티도 함께 영속 상태로 만들 때 사용하는 것이 영속성 전이이다.
다시 말해 영속성 전이를 사용하면 부모 엔터티를 저장할 때 자식 엔터티도 같이 저장할 수 있다.

JPA에서 엔터티를 저장할 때 연관된 모든 엔터티는 영속 상태여야 한다.

일반적으로 CASCADE는 연관관계의 주인에 반대쪽에 설정한다.

CASCADE의 종류

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

CASCADE는 다음처럼 여러 속성을 같이 사용할 수 있다.
cascade = {CascadeType.PERSIST, CascadeType.REMOVE}

+ CascadeType.PERSIST와 CascadeType.REMOVE는 em.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고, 플러시를 호출할 때 전이가 발생한다.

고아 객체

JPA에서 부모 엔터티와 연관관계가 끊어진 자식 엔터티를 자동으로 삭제하는 기능을 고아 객체 제거라 한다.

  • 설정 방법 : orphanRemoval = true

고아 객체 정리

  • 고아 객체 제거는 참조가 제거된 엔터티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야 함
  • 특정 엔터티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능

+ 고아 객체 제거는 추가적으로 CascadeType.REMOVE와 같은 기능을 제거한다.

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

CascadeType.ALL, orphanRemoval = true를 동시에 사용하면 엔터티 스스로 생명주기를 관리한다는 뜻이다. 두 옵션을 모두 활성화하면 부모 엔터티를 통해 자식의 생명주기를 관리할 수 있다.

Cascade.REMOVE vs 고아 객체 제거

처음에는 고아 객체 제거가 Cascade.REMOVE와 같은 기능을 한다고 잘못 이해해서 CascadeType.ALL을 설정하면 굳이 고아 객체 기능도 설정해줘야 할까 하는 의문이 생겼다.

하지만 교재를 천천히 살펴보니 둘이 같은 기능을 하는 것이 아닌, 고아 객체 제거에서 추가적으로 CascadeType.REMOVE 기능을 제공하는 것이었다.

그럼 CascadeType.REMOVE와 고아 객체 제거는 정확히 어떻게 다른 것일까?

CascadeType.REMOVE는 부모 객체가 제거되면 부모 객체 아래 있는 자식 객체도 같이 제거되는 것이다.

반면 고아 객체 제거는 부모 객체는 살아있되, 그 안에서 자식을 삭제하면 부모 객체와 자식 객체 간의 연관관계가 끊어지게 되므로 이 끊어진 자식 엔터티를 제거해주는 것이다.
이때 개념적으로 부모가 제거되어도 자식들은 연관관계가 끊어지므로 제거되는데 이것이 CascadeType.REMOVE와 같다는 것이다.

profile
성장ing

0개의 댓글