[자바 ORM 표준 JPA 프로그래밍 - 기본편] 6. 프록시와 연관관계 관리

김수현·2022년 9월 23일
0

JPA

목록 보기
6/9
post-thumbnail

해당 카테고리는 김영한님의 인프런 강의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 듣고 내용을 정리하기 위한 것으로 자세한 설명은 해당 강의를 통해 확인할 수 있습니다.
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 프록시

우선 프록시를 사용하는 이유에 대해 알아보기 위해 다음의 경우를 살펴보자.

Member를 조회할 때 Team도 함께 조회해야 할까?


회원과 팀 함께 출력

회원만 출력

위와 같을 경우 비즈니스 로직 상 회원과 팀을 함께 출력하는 경우가 많다면 데이터베이스에서 Member를 조회할 때 Team도 함께 조회하는 것이 최적화 부분에서 더 나을 것이고, 회원만 출력하는 경우가 많다면 Member만 조회하고 Team은 선택적으로 조회하는 것이 더 나을 것이다.

이러한 고민을 해결해주는 것이 바로 프록시이다.


📜 프록시 기초

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


📜 프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)

  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출


📜 프록시 객체의 초기화

📜 프록시 핵심

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
    • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

참고: 사실 실무에서 getReference() 메소드를 사용하는 일은 드묾. 이를 설명하는 이유는 이후에 설명할 즉시 로딩과 지연 로딩을 이해하기 위함임


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

참고: JPA 표준은 강제 초기화 없음
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 즉시 로딩과 지연 로딩

이제 위에서 살펴보았던 예를 다시 한 번 살펴보자.


📜 지연 로딩

Member를 조회할 때 Team도 함께 조회해야 할까?

단순히 Member 정보만 사용하는 비즈니스 로직이라면 지연로딩 LAZY를 사용하여 프록시로 조회할 수 있다.

이는 즉 Member를 조회하는 시점에 Team을 즉시 로딩하지 않고 실제 Team 객체를 사용하는 시점에 초기화(DB조회)하는 것이다.

📜 즉시 로딩

Member와 Team을 자주 함께 사용한다면?
만약 비즈니스 로직 상 Member와 Team을 자주 함께 사용한다면 즉시 로딩 EAGER을 사용해서 Member를 조회하는 시점에 Team도 함께 조회하면 될 것이다.
이때, JPA 구현체는 가능하면 조인을 사용해서 SQL로 한번에 함께 조회할 것이다.

📜 프록시와 즉시 로딩 주의

  • 가급적 지연 로딩만 사용할 것(특히 실무에서)
    • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정해주자
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 지연 로딩 활용

우리의 비즈니스 로직은 주로 다음과 같이 동작한다고 가정해보자.

  • Member와 Team은 자주 함께 사용 -> 즉시 로딩
  • Member와 Order는 가끔 사용 -> 지연 로딩
  • Order와 Product는 자주 함께 사용 -> 즉시 로딩

참고: 위는 이론적인 경우이며, 실무에서는 가급적 지연 로딩만 사용하자
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 영속성 전이: CASCADE

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


📜 영속성 전이: 저장


📜 영속성 전이: CASCADE - 주의

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

📜 영속성 전이: CASCADE - 종류

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

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 고아 객체

  • 고아 객체란 부모 엔티티와 연관관계가 끊어진 자식 엔티티로써 JPA는 고아 객체를 자동으로 삭제시켜준다.
  • orphanRemoval = true

예를 들어, 다음과 같이 자식 엔티티를 컬렉션에서 제거 할 경우

JPA는 자동으로 아래와 같은 SQL을 실행한다.


📜 고아 객체 - 주의

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

참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.


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

  • CascadeType.ALL + orphanRemovel=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

📝 실전 예제 - 연관관계 관리

글로벌 페치 전략 설정

  • 모든 연관관계를 지연 로딩으로
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경


영속성 전이 설정

  • Order -> Delivery를 영속성 전이 ALL 설정
  • Order -> OrderItem을 영속성 전이 ALL 설정

profile
안녕하세요 [22.06.19 개설]

0개의 댓글