본문 바로가기
Study/JPA

프록시

by _비니_ 2024. 7. 25.

프록시란?

실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체를 의미

 

  • em.find()
    • 데이터베이스를 통해서 실제 Entity 객체 조회 (쿼리 날라감)
  • em.getReference()
    • 데이터베이스 조회를 미루는 가짜(프록시) Entitty 조회
    • (DB에 쿼리가 안 나가는데 객체가 조회가 됨.)

 

//Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());

//System.out.println("findMember.id = " + findMember.getId());
//System.out.println("findMember.username = " + findMember.getName());

 

아래 출력문들을 주석처리하고, getReference만 호출했을 때는 select 쿼리가 나오지 않고 끝나는 것을 확인할 수 있음.

 

//Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());

System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getName());

 

아래 출력문들을 주석을 해제한다면??

  • System.out.println("findMember.id = " + findMember.getId()); 는 DB를 건들지 않기에 쿼리가 나가지 않음
  • ⇒ em.getReference(Member.class, member.getId());를 할 때 이미 ID 값을 넣었기에 이미 값이 있음 (파라미터로 넣음)
  • ⭐System.out.println("findMember.username = " + findMember.getName());에서 Name은 DB에 있기에 쿼리가 나감
    • .getName()을 호출하는 시점에 DB에 쿼리를 날려 Name을 찾고 findMember의 값을 채워 출력

 

System.out.println("findMember = " + findMember.getClass());

  • findMember.getClass() 를 출력하면 이름이 Member$HibernateProxy$Hz1uC9Un 로 출력
    • 하이버네이트가 강제로 만들어낸 가짜 클래스라는 의미 (프록시 클래스)

 

  • 하이버네이트가 자기 내부의 라이브러리를 사용해 ‘프록시’라고 하는 가짜 엔티티를 줌.
  • 위 그림을 보면) 외부는 똑같지만, 내부는 텅텅 비어있는 것.

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐 (하이버네이트가 내부적으로 라이브러리를 사용해 만들어냄)
  • 실제 클래스와 겉 모양은 같음
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨 (이론상)

 

  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출
    • if getName()을 호출한다면 → 실제 타겟에 있는 getName()을 대신 호출해줌.
    • 처음에는 타겟이 없음. (실제 DB에서 조회한 적이 없기 때문)

 

프록시 객체의 초기화

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

 

getReference 이므로 프록시 객체를 가져오게 됨 (실제 객체 X)

 

  1. member.getName();을 호출할 때 초반에는 DB에 객체가 없으므로
  2. JPA가 ⭐영속성 컨텍스트에 요청⭐을 함
    1. 영속성 컨텍스트를 통해 초기화 요청
    2. 초기화 : DB를 통해 실제 값을 가져와 엔티티를 만들어내는 과정
  3. 영속성 컨텍스트는 DB를 조회해
  4. 실제 Entity를 생성해 넣어줌
  5. 그 이후 프록시 target에 실제 객체를 연결시켜줌.

⇒ 그 이후 이제는 getName() 호출하면, 실제 getName()을 통해 반환이 됨

 

 

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

System.out.println("findMember.username = " + findMember.getName());
System.out.println("findMember.username = " + findMember.getName());

 

findMember.getName()을 2번 호출하면

 

 

1번째 : DB에 객체가 없으므로 쿼리 날려 가져옴

2번째 : 타겟이 이미 값이 있으므로 바로 출력

 

 

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
    • 바로 위 예시 코드 참고
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님!⭐ (교체되는 게 아님. 프록시는 유지가 되고 내부의 값만 채워짐)
    • 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것임!
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)⭐
    • 프록시인 객체와 프록시가 아닌 객체는 타입이 맞지 않음
Member member1 = new Member();
member1.setName("member1");
em.persist(member1);

Member member2 = new Member();
member2.setName("member2");
em.persist(member2);

em.flush();
em.clear();

Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId()); //getReference : 가짜 엔티티 조회

System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // false 출력 : 프록시인 객체와 프록시가 아닌 객체는 타입이 맞지 않음
System.out.println("m1 instanceof m2 : " + (m1 instanceof Member)); // true 출력
System.out.println("m1 instanceof m2 : " + (m2 instanceof Member)); // true 출력

 

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

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

Member reference = em.getReference(Member.class, member1.getId());
//위에서 이미 em.find를 했기에 엔티티가 이미 있으므로 em.getReference()를 호출해도 실제 엔티티가 반환됨.
System.out.println("reference = " + reference.getClass());

System.out.println("reality == reference : " + (reality == reference));

 

reality, reference 둘 다 Member (실제 엔티티)가 반환됨. (reference 출력에 Member 뒤에 프록시 어쩌고 붙는 거 출력 X)

 

  • IF 반대로 em.getReference를 먼저 하고, em.find를 이후에 한다면?⇒ 그래야 reality == reference 를 true로 보장하기 때문.
  • 이후 em.find한 결과도 프록시 객체로 반환이 됨.
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    • 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());

em.detach(reference); //reference를 영속성 컨텍스트에서 관리 안 해!

reference.getName();
System.out.println("reference = " + reference.getName());

 

em.detach(reference); 로 reference를 영속성 컨텍스트에서 관리하지 않는다고

선언하면 아래와 같이 proxy를 초기화할 수 없다고 뜸

(영속성 컨텍스트를 통해 DB를 통해 실제 값을 가져와 엔티티를 만들어내므로

이를 닫거나 detach 해버리면 아래와 같이 뜸)

could not initialize proxy [hellojpa.Member#1] - no Session

⇒ 엄청 많이 만나는 예외 !! 기억하깅

반응형

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

프로젝션과 페이징  (0) 2024.07.27
객체지향 쿼리 언어 소개  (0) 2024.07.27
값 타입 2  (0) 2024.07.27
값 타입 1  (0) 2024.07.27
프록시 ) 영속성 전이(CASCADE)와 고아 객체  (0) 2024.07.26