프록시

Posted by sJun on November 06, 2020 ·

JPA에서 프록시가 왜 필요할까요?

Member 객체 내부에 Team 객체가 있다고 생각해봅시다.

Member 를 불러올 때 Team을 불러오는 상황도 있지만 당연히 없는 상황도 있기 마련입니다.

즉 필요없는 상황에서도 Team을 계속 불러오는 것은 낭비일 수 밖에 없습니다.

이런 낭비를 막기 위해 JPA는 ‘프록시’ 라는 것을 사용합니다.

23

그림의 프록시 내부의 target을 통해 원본을 찾아갑니다.

em.find() // 실제 엔티티 객체 조회
em.getReference() // 프록시 객체 조회

프록시 객체를 조회하기 위해 JPA는 getReference() 라는 메서드를 제공합니다.

프록시는 원본 클래스를 상속 받아서 만들어지고, 실제 클래스와 겉모양이 같습니다.

그래서 사용하는 입장에서 진짜 객체인지, 프록시 객체인지 구분하지않고 사용 가능합니다.

JPA에서의 프록시는 초기화 과정이 중요합니다.

1


먼저 Client에서 프록시 객체로 요청을 합니다.

그럼 Proxy 객체는 영속성 컨텍스트에서 초기화를 요청합니다.

영속성 컨텍스트에서 DB 테이블 조회 후 원본 객체에 맞는 실제 Entity를 생성하게 됩니다.

이제 Proxy 내부에 target이 원본 객체를 가리키고 있으므로 더이상 초기화작업 없이 바로 원본 객체를 가져올 수 있습니다.


중요한 점은 프록시 객체가 원본 객체로 ‘대체’ 되는 것이 아니라 실제 엔티티에 ‘접근’이 가능한 것 입니다.

그리고 프록시 객체는 원본 엔티티를 상속 받기 때문에 타입 체크 시 주의해야 합니다.

확인을 위한 테스트 코드를 작성했습니다.

Member member1 = new Member();
member1.setName("kim");
        
Member member2 = new Member();
member2.setName("Lee");

em.persist(member1);
em.persist(member2);

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

Member memberA = em.find(Member.class, member1.getId());
Member memberB = em.find(Member.class, member2.getId());

System.out.println("(memberA == memberB) = " + (memberA.getClass() == memberB.getClass()));
2020-11-06 21:24:09.992 TRACE 46796 --- [    Test worker] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([name6_3_0_] : [VARCHAR]) - [Lee]
(memberA == memberB) = true
2020-11-06 21:24:10.003  INFO 46796 --- [    Test worker] o.s.t.c.transaction.TransactionContext   : Committed transaction for test:

true가 나옵니다. 코드를 다음과 같이 수정합니다.

Member memberA = em.find(Member.class, member1.getId());
Member memberB = em.getReference(Member.class, member2.getId());
[VARCHAR]) - [kim]
(memberA == memberB) = false
2020-11-06 21:28:06.709  INFO 10

false가 나옵니다.

JPA는 같은 클래스로부터의 타입 비교는 같음을 보장해주지만 프록시 객체는 그렇지 않다는 점을 주의하셔야합니다.

올바르게 타입 체크를 하려면 다음과 같아야 true가 나옵니다.

System.out.println("(memberA == memberB) = " + 
((memberA instanceof Member) == (memberB instanceof Member)));

1

여기서 다시 한번 사진을 보겠습니다.

만약 Proxy를 초기화 하려는데 영속성 컨텍스트의 도움을 못받는 상황이라면 어떻게 될까요?

LazyInitializationException 예외가 나옵니다. 코드에서 em.close()나 em.detach() 를 추가해보시면 예외를 터트리는걸 볼 수 있습니다.


하이버네이트는 프록시 확인에 관련된 여러가지 메서드를 지원합니다.

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

PersistenceUnitUtil.isLoaded(Object Entity)

참고로 사용하려면 EntityManagerFactory 인스턴스를 통해 사용이 가능합니다.

프록시 강제 초기화

Hibernate.initialize(entity)

이 초기화 메서드는 하이버네이트에서 지원하므로 JPA 표준에서는 지원하지 않습니다. JPA 표준은 위 코드처럼 member.getName()을 사용하면 초기화 시킬 수 있습니다.