Zayson
A to Zayson!
Zayson
전체 방문자
오늘
어제
  • 분류 전체보기 (132)
    • Computer Science (20)
      • Network (4)
      • DB (12)
      • OS (4)
    • Algorithm (32)
      • 완전탐색(Brute-Force) (3)
      • 그리디(Greedy) (6)
      • 투포인터(Two-Pointer) (1)
      • 그래프(Graph) (5)
      • BFS & DFS (9)
      • 구현, 시뮬레이션(Implementation) (5)
      • 다이나믹 프로그래밍(DP) (3)
    • Backend (51)
      • Spring Boot (19)
      • JPA (16)
      • Kafka (2)
      • Java (13)
      • Kotlin (1)
    • DevOps (1)
      • Jenkins (5)
      • Oracle Cloud Infrastructure (1)
      • Kubernetes & Docker (1)
    • Trouble Shooting (3)
      • JPA (1)
      • Spring Boot (2)
    • 회고 (5)
      • 엔빵 프로젝트 포스트 로드맵 (1)
      • 2022년 (4)
    • Kafka (7)
      • Kafka (5)
      • Kafka Connect (2)
    • 기술 서적 (6)
      • 데이터 중심 애플리케이션 설계 (3)
      • 개발자가 반드시 정복해야할 객체 지향과 디자인 패턴 (2)
      • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • 프로그래머스
  • Backend
  • dfs
  • 나 혼자 스프링부트!
  • 그리디
  • SpringBoot
  • 구현
  • Computer science
  • JPA
  • Java
  • kafka
  • spring boot
  • 엔빵프로젝트
  • 관계형 데이터베이스 실전 입문
  • Kafka Connect
  • 백준
  • 완전탐색
  • BFS
  • 라이브스터디
  • CS

최근 글

티스토리

hELLO · Designed By 정상우.
Zayson

A to Zayson!

프록시
Backend/JPA

프록시

2022. 7. 20. 14:07
  • 연관관계가 있는 엔티티를 조회 시에 상황에 따라 한번에 연관관계 데이터까지 가져오거나 조회를 원한 특정 엔티티만 조회하고 싶은 경우가 있다.
    1. 회원과 팀이 연관관계가 있는 경우 회원 정보를 조회하면서 회원이 속한 팀의 정보도 함께 조회하고 싶은 경우
    2. 회원과 팀이 연관관계가 있는 경우 회원 정보만 조회하고 팀의 정보는 조회하고 싶지 않은 경우
  • JPA는 이러한 상황을 해결하기 위해 프록시와 지연로딩을 통해 해결해준다.

 

프록시 기초

  • em.getReference() : 데이터베이스 조회를 미루는 프록시(가짜) 엔티티 객체를 가져온다. 따라서 쿼리가 실행되지 않는다.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());

// 결과 : findMember = class com.hello.Member$HibernateProxy$ywht19mS
  • hibernate 내부의 라이브러리를 사용해 실제 DB에서 조회한 엔티티를 반환하는 것이 아닌 프록시 엔티티 객체를 반환한다.
    • 껍데기는 동일하지만 내부에는 target이 진짜 엔티티 객체를 가리키고 있다.
  • 실제 엔티티를 상속받아서 사용하기 때문에 실제 엔티티와 프록시의 겉모양은 동일하다.
  • 사용자의 입장에서는 프록시 객체인지 실제 객체인지 몰라도 사용이 가능하다.
  • 프록시 객체는 Entity target으로 실제 객체의 참조를 가지고 있다.

 

  • 프록시 객체의 초기화 
    • getReference() 까지 한 경우 프록시 객체의 target에는 null이다
    • getName()을 통해 프록시 객체로 조회를 시도하는 경우
    • 데이터 조회
    • 프록시 객체가 영속성 컨텍스트에 초기화를 요청, 프록시에 값이 없는 경우 진짜 값을 요청한다.
    • 영속성 컨텍스트는 DB를 조회
    • 실제 엔티티를 생성
    • 프록시 객체의 타겟과 실제 엔티티 매핑

 

프록시 특징

  • 프록시 객체는 처음 사용할 때 (내부의 target이 실제 엔티티의 래퍼런스를 가지고 있지 않은 경우) 한 번 초기화를 한다.
Member findMember = em.getReference(Member.class, member.getId());

//해당 시점에 영속성 컨텍스트에 초기화 요청
System.out.println("findMember.getUsername() = " + findMember.getUsername());

//두번째 요청의 경우 영속성 컨텍스트가 초기화를 했기 때문에 쿼리가 한번더 요청되지 않는다.
System.out.println("findMember.getUsername() = " + findMember.getUsername());

 

  • 프록시 객체가 실제 엔티티 객체로 변환되는 것이 아니다!
findMember.getClass(); // 초기화 이전 -> Proxy (target = null)
findMember.getUsername();  // 영속성 컨텍스트에 초기화 요청 -> proxy가 실제 엔티티 객체 호출 
findMember.getClass(); // 초기화 이후 -> Proxy (target = 실제 엔티티 래퍼런스)

 

  • 프록시 객체는 원본 엔티티를 상속받기 때문에 타입 체크시 instance of를 사용한다.
Member findMember1 = em.find(Member.class, member1.getId());
Member findMember2 = em.getReference(Member.class, member2.getId());

// false
System.out.println("(member1.getClass() == member2.getClass()) = " + (findMember1.getClass() == findMember2.getClass()));

// true
System.out.println("member1 instanceof Member = " + (findMember1 instanceof Member));
System.out.println("member2 instanceof Member = " + (findMember2 instanceof Member));

 

  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
    • 영속성 컨텍스트 1차 캐시에 있는데 굳이 프록시를 가져올 필요가 없다.
    • 한 트랜잭션 내에서 실제 엔티티, 프록시 상관없이 한 영속성 컨텍스트 내에서 동일한 PK를 가지는 경우
      JPA는 “==”이 true임을 보장
      한다.
// 영속성 컨텍스트에 올라간 상태
Member findMember1 = em.find(Member.class, member1.getId());
System.out.println("findMember1.getClass() = " + findMember1.getClass());

Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference.getClass() = " + reference.getClass());

// true (실제 엔티티 클래스 반환)
System.out.println("(findMember1 == reference) = " + (findMember1 == reference));

== 반대 경우 ==

Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember.getClass() = " + refMember.getClass());

Member findMember1 = em.find(Member.class, member1.getId());
System.out.println("findMember1.getClass() = " + findMember1.getClass());

// true (프록시 반환)
System.out.println("(refMember == findMember1) = " + (refMember == findMember1));

 

  • 준영속 상태인 경우 프록시를 초기화하면 예외가 발생한다. (LazyInitializationException)
    • 트랜잭션과 영속성 컨텍스트 라이프 사이클을 일반적으로 맞추는데 트랜잭션이 끝나고 프록시를 초기화하는 경우 예외발생
// 영속성 컨텍스트에 올라간 상태
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember.getClass() = " + refMember.getClass());   // 프록시

em.detach(refMember);   // 영속성 컨텍스트에서 꺼냄

// 영속성 컨텍스트를 이용해 프록시를 초기화해야하는데 영속성 컨텍스트에서 꺼낸 상태므로 예외
System.out.println("refMember.getUsername() = " + refMember.getUsername());

 

  • 프록시를 초기화 했는지 확인하는 메서드 : emf.getPersistenceUnitUtil().isLoaded(refMember)
  • Hibernate 프록시 강제 초기화 메서드 : Hibernate.initialize();
  •  

📃 References

김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

반응형
저작자표시 비영리 변경금지 (새창열림)

'Backend > JPA' 카테고리의 다른 글

영속성 전이 (Cascade), 고아 객체  (0) 2022.07.21
즉시 로딩과 지연 로딩  (0) 2022.07.21
상속관계 매핑  (0) 2022.07.20
엔티티 설계의 주의사항 간단 정리  (0) 2022.07.05
다양한 연관관계 매핑  (0) 2022.05.02
    'Backend/JPA' 카테고리의 다른 글
    • 영속성 전이 (Cascade), 고아 객체
    • 즉시 로딩과 지연 로딩
    • 상속관계 매핑
    • 엔티티 설계의 주의사항 간단 정리
    Zayson
    Zayson
    공부한 내용을 정리하는 공간

    티스토리툴바