프록시

JPA
2023. 11. 9. 20:39
목차
  1. 프록시
  2. 프록시 기초
728x90

프록시

프록시 사용하는 이유

비즈니스 로직에서 Member와 Team을 같이 조회해야할 지는 상황에 따라 달라진다.

만약 회원과 팀을 함께 출력해야하면 회원 정보를 조회할 때 Team 정보도 같이 조회하는게 좋지만,

회원 정보만 필요한 경우 Team 정보까지 필요하지 않다.

em.find(Member.class, 1L)에서 team도 조회할지 회원 정보만 조회할지 상황에 따라 달라진다

 

이런 문제를 지연 로딩과 프록시로 해결할 수 있다.

 

프록시 기초

em.find() vs get.Refernece()

  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조
try {
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.getReference(Member.class, member.getId());
System.out.pintln("findMember.id = " + findMember.getId());
System.out.pintln("findMember.id = " + findMember.getUsername());
tx.commit();
}

 

결과를 보면 getUsername() 조회할 때만 쿼리가 날라갔다.

→ em.getReference 호출하는 시점에는 쿼리를 안날리고 findMember가 실제로 사용되는 시점에 쿼리가 나가는데 member.getId()할 때 쿼리가 안나가는 이유는 em.getReference(Member.class, member.getId())에서 파라미터로 getId() 해서 findMember에 값이 있기 때문에 findMember.getId() 할 때는 쿼리가 날라가지 않고, Username은 아직 DB에 값이 있다.

따라서 findMember.getUsername()에서 쿼리가 날라가서 데이터를 가져온다.

 

em.getReference해서 가져온 객체

 

결과를 보면 em.getReference(Member.class, member.getId())로 가져온 클래스는 하이버네이트가 만들어낸 가짜 클래스이다.

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어졌다

 

  • 실제 클래스와 겉 모양이 같다.

 

  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용됨 (이론상).

 

  • 프록시 객체는 실제 객체의 참고(target)을 보관

 

  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드 호출(프록시의 getName()을 호추라면 실제 엔티티의 getName() 대신 호출해준다.)
    • 엔티티를 DB에서 조회하기 전까지 target에 엔티티가 없다.

 

  • 프록시 객체는 처음 사용할 때 한 번만 초기화

 

  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니라 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것이다.

 

  • 프록시 객체는 원본 엔티티를 상속 받으므로 프록시 타입 체크할 때 주의해야한다
    • == 비교하면 안되고 instance of 사용

 

  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환

 

reference 결과: 실제 엔티티 반환

 

프록시 객체를 먼저 조회하는 경우 실제 엔티티로 조회해도 둘다 프록시 객체로 나온다.

  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생한다.

 

하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림

 

프록시 초기화 요청이 영속성 컨텍스트를 통해서 일어나므로 em.detach(reMember) (refMember를 영속성 컨텍스트가 관리하지 않게..) 또는 em.close() (영속성 컨텍스트 닫으면..) 또는 em.clear() refMember는 영속성 컨텍스트의 도움을 받지 못하므로 refMember.getUsername()에서 프록시를 초기화 할 수 없다고 에러가 난다. 

 

프록시 객체는 처음 사용할 때 한번만 초기화 되므로 refMember.getUseraname()에서는 초기화되지 않고 영속성 컨텍스트가 clear되니까 실제 엔티티에 접근하지 못하므로 에러 발생

프록시 객체 초기화

Member member = em.getReference(Member.class, member.getId());
member.getName();

프록시 객체 초기화 과정

em.getReference하고 껍데기만 있는 프록시가 만들어지고 member.getName해서 프록시의 getName()을 보는데 맨 처음에는 Member target에 데이터가 없다.

 

JPA가 영속성 컨텍스트에 Member 객체요청 → 영속성 컨텍스트가 DB 조회 → DB는 실제 entity 생성해서 MemberProxy의 target에 entity 연결 → target.getName()을 통해서 Member의 getName()이 반환

 

 

Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.username = " + findMember.getUsername());

위 코드에서는 getUsername()할 때 초기화 과정이 일어난다. 따라서 getUsername()을 두번 요청하면 초기화 과정이 필요하지 않다.

 

프록시 확인 방법

 

프록시 인스턴스 초기화 여부 확인

PersistenceUnitUtil.isLoaded(Object entity)
//entity: 프록시 객체
//초기화된 프록시 객체 -> true
//초기화 되지 않은 프록시 객체 -> false

 

프록시 클래스 확인 방법

entity.getClass();
//entity: 프록시 객체

출력: (..javasist.. or HibernateProxy…)

 

프록시 강제 초기화

org.hibernate.Hibernate.initialize(entity);
//Hibernate.initialize(proxyEntity);

 

참고: 하이버네이트가 강제 초기화를 제공하는 거고 JPA 표준은 강제 초기화 없다.

 

QnA

Q. 프록시 객체도 영속성 컨텍스트에 저장이 되나?

프록시 객체 초기화할 때 영속성 컨텍스트에 초기화 요청하고 영속성 컨텍스트가 DB에서 조회한다고 나왔는데 .getReference 했을 때 생성되는 프록시 객체도 영속성 컨텍스트에서 관리하나?

 

A. 맞다. 참고할 자료: https://tecoble.techcourse.co.kr/post/2022-10-17-jpa-hibernate-proxy/

 

728x90

'JPA' 카테고리의 다른 글

영속성 전이: CASCAED  (0) 2023.11.09
즉시 로딩과 지연 로딩  (2) 2023.11.09
고급 매핑  (0) 2023.11.09
양방향 연관관계와 연관관계의 주인  (0) 2023.11.02
연관관계 매핑 기초  (0) 2023.11.02
  1. 프록시
  2. 프록시 기초
'JPA' 카테고리의 다른 글
  • 영속성 전이: CASCAED
  • 즉시 로딩과 지연 로딩
  • 고급 매핑
  • 양방향 연관관계와 연관관계의 주인
hapBday
hapBday
hapBday
개발자로 성장하기 위한 기록들
hapBday
전체
오늘
어제
  • 분류 전체보기 (205)
    • CS (12)
      • 컴퓨터네트워크 (11)
      • 운영체제 (0)
      • 분산 시스템 (0)
      • 데이터베이스 (1)
    • Spring (47)
      • Spring 핵심 원리 (13)
      • Spring MVC (15)
      • Spring DB (12)
      • Spring Security (6)
    • JPA (14)
    • 알고리즘 (30)
      • 프로그래머스 (6)
      • 백준 (20)
    • Design Pattern (0)
    • 언어 (5)
      • JAVA (5)
    • ASAC 웹 풀스택 (38)
      • Spring Boot (21)
      • React (0)
      • DevOps (8)
    • 트러블슈팅 (17)
    • DevOps (5)
      • Docker (5)
    • ETC (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • github

공지사항

  • 블로그 이전

인기 글

태그

  • x-lock
  • 티스토리챌린지
  • CORS
  • cookie
  • 트랜잭션
  • S3
  • 프로그래머스
  • multi-stage
  • 인프런
  • Java
  • MVC
  • 백준
  • docker workflow
  • Spring
  • 김영한
  • Session
  • CSRF
  • jwt
  • docker
  • spring security
  • 오블완
  • basicerrorcontroller
  • docker best practices
  • currency control
  • 구현
  • s-lock
  • 3-layerd 아키텍쳐 패턴
  • JPA
  • spring boot
  • aws lambda

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.3.0
hapBday
프록시
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.