프록시

경우에 따라 Member만 조회/ Member와 Team도 함께 조회할 때

Member를 조회할 때 불필요하게 Team도 함께 조회가 된다면?

구현체마다 내부 매커니즘이 조금씩 다르다. 아래 설명은 Hibernates에 해당한다.

SQL 쿼리가 나가지 않았는데 데이터를 조회했다! 1시점에서는 DB에 쿼리가 안나가고 사용되는 시점에 쿼리가 나간다. 2시점에서 id는 1에서 파라미터로 가져왔기 때문에 영속성 컨텍스트에서 값을 얻고, 3시점에서는 영속성 컨텍스트에 username 데이터가 없으므로 이 때 DB 조회하는 쿼리를 날린다.

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

• 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

처음에 프록시 객체 MemberProxy 는 값이 없다.Member tartget이 비어있다. 사용자가 .getUsername()을 호출하면 프록시 객체는(JPA가) 영속성 컨텍스트에 초기화 요청한다. 그러면 영속성 컨텍스트는 DB를 조회하고, 실제 Entity를 생성해서 프록시 객체의 멤버변수 target과 연결한다. 그래서 target.getUsername()으로 실제 엔티티의 getUsername을 조회할 수 있는 것이다.

프록시 내부 동작 방식
  1. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능! 프록시는 유지되고 내부에 값만 채워지는 것이다! 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다!

  2. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해 도 실제 엔티티 반환 ① em.find(Member.class,mem1.getId()); 로 영속성 컨텍스트에 mem1 엔티티 존재 ② em.getReference(Member.class,mem.getId()) : 영속성 컨텍스트에 mem1 엔티티가 존재하기 때문에 실제 엔티티를 가져온다!!! 타입 확인 : ① = Member 클래스, ② = Member 클래스 이유 JPA는 동일한 트랜잭션. 동일한 영속성 컨텍스트 내에서 객체 인스턴스 '==' 비교를 하면 항상 같다고 나와야 한다. 한 트랜잭션 내에서 엔티티 동일성을 보장해준다! (단, em.clear()를 하고 면 영속성 컨텍스트를 비우기 때문에 ②는 프록시 객체가 되고 ①과 타입 비교 시 다르기 때문에 false로 나온다.)

  3. 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용) 두 객체를 비교할 때 각각 실제 엔티티인지 프록시인지 알 수 없기 때문에== 로 비교하면 안 된다!!!

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


    (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림) 프록시를 초기화한다는 것은 프록시 객체로 값을 조회한다는 것을 의미한다. 사용자가 조회하려는 객체가 준영속 상태인 줄 모르고 값을 조회하려고 할 때, 이 객체가 프록시일 때 오류가 난다! 왜냐하면 준영속 상태이면 영속성 컨텍스트에 존재하지 않기 때문에 초기화하려는 값이 없기 때문이다. - em.detach(member1) - em.clear() - em.close()

5. 프록시 확인

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

    PersistenceUnitUtil.isLoaded(Object entity)
  • 프록시 클래스 확인 방법
entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)

    entity.getClass(member1)
  • 프록시 강제 초기화 : 
org.hibernate.Hibernate.initialize(entity);

    Hibernate.initialize(member1);
  • 참고: JPA 표준은 강제 초기화 없음
 강제 호출: 바로 엔티티(객체)의 getter 호출 해버림.

    member1.getName();

실제로는 getReference 많이 사용하지 않음. 하지만 프록시 개념을 깊이있게 이해하기 위함. 프록시를 깊이 있게 이해해야 즉시 로딩(EAGER) 지연 로딩(LAZY)를 깊이있게 이해할 수 있기 때문

  • 타입 비교 시 주의 사항 타입이 정확하게 일치해야 같다. 상속관계 => 같지 않다!

Last updated