[JPA] Proxy(프록시)

동동·2022년 4월 23일
0

JPA

목록 보기
9/18
post-thumbnail

Proxy를 왜 사용하게 됐을까?
위 질문에 답하기 전에 만약 Member를 조회할 때 Team도 함께 조회를 해야 할까? 하는 질문에 대한 대답을 생각해보자

private static void searchMember(Member member) {
	member.getUsername();
}

멤버의 이름을 조회하는 코드를 실행할 때 만약 Team까지 같이 조회한다면 낭비가 발생한다. 다시 말해 최적화가 안되었다는 뜻이다. 이런 문제를 해결하기 위해 사용하는 것이 프록시이다.

Proxy 기초

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

getReference()를 사용하는 시점에는 DB에 쿼리를 날리지 않음
데이터가 실제 사용되는 시점에 DB에 쿼리를 날림

Proxy객체에는 Entity target이란 것이 있다. Entity target은 실제 엔티티의 위치를 나타낸다. 초기에는 null값이 들어있다.


그리고 초기화를 하면 프록시 객체는 영속성 컨텍스트에 초기화 요청을 하고 영속성 컨텍스트는 DB를 조회한다 그럼 실제 Entity가 생성되고 그 Entity의 위치가 프록시 객체 target에 저장된다.

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

class를 조회했을 때 getReference()를 이용해 조회하면 클래스 이름 뒤에 Proxy주소가 나온다. 즉, 하이버네이트가 강제로 만든 가짜 클래스라는 얘기다.

Proxy 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체이니 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프로시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속 받음. 따라서 타입 체크시 주의해야 함(== 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
// 실제 엔티티가 영속성 컨텍스트에 올라감
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());

// getReference()를 이용해 객체를 조회해도 이미 엔티티가 영속성 컨텍스트에 올라가 있기 때문에 실제 엔티티가 조회됨
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());

// 같은 트랜잭션 내에 있는 엔티티는 같은 엔티티를 반환해야 함
// 따라서 em.find()로 객체를 조회 했어도 프록시를 반환할 수가 있음
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    (하이버네이트는 org.hibernate.LazyInitializationException 예외 발생)
private static void logic(Meber m1, Member m2) {
	// true return
    Member m1 = em.find(Member.class, member1.getId());
    Member m2 = em.find(Member.class, member2.getId());
	System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass()));
    
    // false return
    Member m1 = em.find(Member.class, member1.getId());
    Member m2 = em.getReference(Member.class, member2.getId());
}

// 실제로는 이렇게 메서드를 이용하는 경우가 많음. 매개변수로 실제 엔티티가 넘어올지 프록시가 넘어올지 알 수 없음
private static void logic(Member m1, Member m2){
	System.out.println("m1 == m2: " + (m1.getClass() == m2.getClass()));
    // 따라서 == 비교 대신 instanceof를 사용해야함
    System.out.println("m1 == m2: " + (m1 instanceof Member));
    System.out.println("m1 == m2: " + (m2 instanceof Member));
}
  • 프록시의 초기화 요청은 영속성 컨텐스트를 통해 일어남
    만약 영속성 컨텍스트를 꺼버린다면?
    em.detach() / em.clear() / em.close()

Proxy 확인

  • Proxy instance의 초기화 여부 확인
    PersistenceUnitUtil.isLoaded(Object entity)

  • Proxy Class 확인 방법
    entity.getClass().getName() 출력

  • Proxy 강제 초기화
    org.hibernate.Hibernate.initialize(entity);

  • 참고 : JPA는 강제 초기화 없음
    강제 호출 : member.getName()

profile
괴발개발

0개의 댓글