BACK/JPA

[JPA] JPA 프록시의 특징

연듀 2024. 4. 2. 15:06

 

프록시

JPA는 em.find() 말고도 참조를 가져오는 em.getReference() 라는 메소드가 있다.

em.find() : 데이터베이스를 통해 실제 엔티티 객체를 바로 조회

em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회 (DB쿼리 안나감)

 

Member member = new Member();
member.setUsername("hello");

em.persist(member);

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

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

 

이렇게 했을 때 멤버와 팀을 조인해 다 select로 가져온다.

 

Member findMember = em.getReference(Member.class, member.getId()); // 프록시 객체 조회
System.out.println("findMember.id() = " + findMember.getId()); // id는 reference를 찾을 때 이미 있기 때문에 DB 쿼리 안나감
System.out.println("findMember.getUsername() = " + findMember.getUsername()); // DB 쿼리 나감

 

em.getReference만 호출하면 쿼리가 안나가고,

실제 사용하는 시점인 findMember.getUsername()를 호출하는 시점에 select 쿼리가 나간다.

 

System.out.println("findMember = " + findMember.getClass());
// findMember = class hellojpa.Member$HibernateProxy$rsth0FRe

 

findMember.getClass()를 하면 주석과 같은 이름의 클래스임을 볼 수 있다.

 

hibernate가 강제로 만든 가짜 클래스라는 것이다.

(= 프록시 클래스)

 

 

프록시 특징

  • 실제 클래스를 상속 받아 만들어져 실제 클래스와 겉 모양이 같다.
  • 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)
  • 프록시 객체는 실제 객체의 참조를 보관한다.
  • 프록시 객체를 호출 하면 프록시 객체는 실제 객체의 메소드를 호출한다.

 

프록시 객체의 초기화

 

처음에 member.getName()을 호출할 때, Member target이 없으면 영속성 컨텍스트에 진짜 객체를 요청한다.

영속성 컨텍스트는 DB를 조회해 실제 엔티티 객체를 생성해 준다.

그리고 프록시 객체의 타겟에 연결해준다.

그리고 타겟의 getName()을 통해 Member의 getName()이 반환된다.

만약 두번 findMember.getName() 한다면, 처음에는 DB 조회를 하고 그 다음에는 타겟의 값이 있으니 이미 초기화가 되어있는 프록시에서 값을 출력하게 되는 것이다.

 

 

 

프록시의 특징

 

  • 프록시 객체는 처음 사용할 때 한번만 초기화
  • 프록시 객체를 초기화 할 때 프록시 객체를 통해 실제 엔티티에 접근하는 것이지, 프록시 객체가 실제 엔티티로 바뀌는게 아니다.
  • 프록시는 원본 엔티티를 상속받기 때문에, 타입 체크시 == 비교대신 instanceof 를 사용해야 한다.
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다. 

 

Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

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

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


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

System.out.println("a==a = " +(m1==reference)); // 한 영속성 컨텍스트에서 가져온 것이고 같은 PK 이면 true

 

 

 

 

이미 프록시를 반환한 상태면 em.find()해도 프록시를 반환한다.

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

Member findMember = em.find(Member.class, member1.getId()); // 프록시를 한번 반환하면 find해도 프록시를 반환한다.
System.out.println("reference.getClass() = " + findMember.getClass());

System.out.println("refMember==findMember : " +(refMember==findMember)); // true

 

 

 

 

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

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

em.detach(refMember); // 준영속 상태
// em.close();
// em.clear();

refMember.getUsername(); // 실제 db 쿼리 나가며 프록시 객체 초기화,
// 만약 detach 한다면 영속성 컨텍스트가 refMember를 관리를 안하게 되므로 could not initialize proxy 예외 터짐

 

 

 

 

인프런 자바 ORM 표준 JPA 프로그래밍 - 기본편을 수강하고 정리한 글입니다.

https://www.inflearn.com/course/ORM-JPA-Basic