프록시

D A S H B O A R D
D E V E L O P
S E C U R I T Y
 프록시
 프록시 예제
 프록시 객체의 초기화
 프록시의 특징
 프록시 확인
Reference

 프록시

프록시는 JPA에서 엔티티를 지연로딩할 때 사용되는 객체입니다. 지연로딩이란 실제로 사용될 때 로딩하는 방식으로, 해당 엔티티를 조회할 때 그 엔티티의 필드가 필요한 경우에만 DB에서 조회를 수행합니다. 이 때 프록시는 실제 엔티티 대신 사용되며, 프록시의 필드에 접근하려고 할 때 실제 엔티티를 로딩하여 반환합니다.
예를 들어, A 엔티티와 B 엔티티가 있고, A 엔티티는 B 엔티티를 참조하고 있습니다. B 엔티티를 지연로딩으로 설정하면, A 엔티티를 조회할 때는 B 엔티티를 조회하지 않습니다. 대신 A 엔티티의 B 필드에 접근하려고 할 때 프록시 객체를 반환하며, 이때 B 엔티티를 로딩하여 반환합니다. 이를 통해 성능 향상을 기대할 수 있습니다.
지연 로딩은 다음 글 참고!!
지연로딩? 프록시? 왜 사용할까?
위와 같은 객체가 있다고 가정하자, Member라는 엔티티는 team이라는 엔티티와 연관관계 매핑이 되어 있다.
회원과 팀 함께 출력
이 때 내가 Member를 불러 올때 DB의 관점에서는 당연히 Team 엔티티까지 Join을 통해 가져오는 것이 맞다.
회원만 출력
하지만, Member 엔티티를 불러와서 Team을 사용하지 않을 것이라면 Team 엔티티까지 Join을 통해 가져 오는 것은 굉장히 비효율적인 일이다
즉, 사용하는 순간까지 DB 조회를 미루기 위해 사용한다.

 프록시 예제

em.find() vs em.getReference()
em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
Member findMember = em.getReference(Member.class, member.getId()); System.out.println("findMember = " + findMember.getId()); System.out.println("findMember = " + findMember.getUsername());
Java
복사
원래라면은 em.find() 를 통해 엔티티에 대해 DB에서 조회를 했어야 한다. 하지만,
위 코드를 실행시켜 보면, getID()를 출력할 때에는 이미 getId()를 통해 findMember를 가지고 왔음으로 영속성 컨텍스트 안에 저장되어 있다. 따라서 이 때는 DB조회를 하지 않게 된다.
getUsername()을 이용할 때되서야 이 값이 비어있기 때문에, DB에 조회를 하게된다.
아래로 내려가면서 점점 궁금증을 풀어보자!!!

 프록시 객체의 초기화

위에서 했던 코드가 바로 프록시 객체의 초기화이다.
1.
getName()을 호출했을 때 Proxy객체에 Name이 없다면?
2.
영속성 컨텍스트로 초기화 요청이 들어간다.
3.
영속성 컨텍스트는 DB 조회를 통해 실제 Entity를 받아오고
4.
Proxy는 실재 객체의 매서드를 호출하게 된다.
여기서 알 수 있는 점!!
Proxy 객체는 영속성 컨텍스트가 관리한다.
객체는 Proxy 객체가 실제 엔티티로 교체가 되는 것이 아니라 그대로 Proxy 객체가 사용된다.

 프록시의 특징

실제 클래스를 상속 받아서 만들어짐
실제 클래스와 겉 모양이 같다.
사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
proxy를 이용할 경우, 엔티티 객체를 상속 받은 Proxy객체가 호출된다.
Member findMember = em.getReference(Member.class, member.getId()); System.out.println("findMember의 클래스는? : " + findMember.getClass());
Java
복사
프록시 객체는 실제 객체의 참조(target)를 보관
프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
즉, Proxy로 호출했으면 영속중인 동안에는 계속 Proxy 엔티티로 존재한다.
특징!!!
1.
프록시 객체는처음사용할때 한번만 초기화
2.
프록시객체를초기화할때,프록시 객체가 실제 엔티티로 바뀌는것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능 ⇒ 1,2번을 보면  프록시 객체의 초기화 의 콜아웃부분에 이야기한 것과 동일 한 이야기
프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용) → 프록시 객체는 원본 엔티티를 상속받은 객체로서 엄연히 원본과는 다르기 때문에 m1 instance of Member 와 같은 형식으로 비교해야 한다.
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환 → 영속성 컨텍스트가 Proxy 객체를 관리하기 때문에, 굳이 Proxy 객체를 호출하지 않는다.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림) ⇒ 얘는 진짜 많이 많이 보게 될 오류이니 기억하자!!!!

 프록시 확인

프록시 인스턴스의 초기화 여부 확인 : PersistenceUnitUtil.isLoaded(Object entity)
Member refMember = em.getReference(Member.class, member.getId()); System.out.println("refMember = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
Java
복사
프록시 클래스 확인 방법 : entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)
프록시 강제 초기화 : org.hibernate.Hibernate.initialize(entity);
Member refMember = em.getReference(Member.class, member.getId()); Hibernate.initialize(refMember);
Java
복사
참고: JPA 표준은 강제 초기화 없음 → 강제 호출: member.getName()