프록시(Proxy)
📌 프록시란
실제 클래스를 상속받아 만들어진 가짜 또는 원본을 위임받은 가벼운 객체
프록시 객체는 getClass() 정보에 "HibernateProxy"라는 정보가 붙는다.
📌 프록시 조회
- em.find() : 영속성 컨텍스트로부터 타깃 엔티티를 조회, 만약 영속성 컨텍스트에 타깃이 없다면 DB에서 조회 후 영속성 컨텍스트에 저장 ➡ SQL 전송 o, 조회 o
- em.getReference() : DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회 ➡ SQL 전송 x, 조회 o
- getReference() 시점이 아닌 getter 시점에 DB에 조회 SQL을 날림
📌 특징
1. 프록시는 실제 객체의 참조(target)을 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출
2. 실제 클래스를 상속받아 만들어지므로, 사용하는 입장에서는 구분하지 않고 사용 가능하다.
3. 프록시 객체는 처음 사용할 때 한 번만 초기화한다. 즉, 초기화한 값을 계속 사용하고 더 이상 값을 초기화하지 않는다.
4. 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 교체되는 것이 아니다.
- 단지, 초기화되면 프록시 객체의 target 값이 채워져 실제 엔티티에 접근이 가능해진다.
5. 프록시 객체는 원본 엔티티를 상속받으므로, 타입 체크 시 '==' 대신 instance of를 사용해야 한다.
- 해당 엔티티가 프록시일지 실제일지 모르기 때문에 JPA는 웬만하면 instance of를 사용하는 것이 좋다.
6. em.find를 통해 영속성 컨텍스트에 실제 엔티티가 이미 들어 있다면 em.getReference를 호출해도 실제 엔티티를 반환
- 이미 원본을 가져온 상태에서 프록시를 내어 얻는 이점이 없다. (성능 최적화에도 원본이 유리)
7. JPA는 한 트랜잭션/영속성 컨텍스트 내에서 같은 엔티티를 조회했을 때 같음을 보장한다. (동일성 보장)
- 엔티티든 프록시든 한 트랜잭션/영속성 컨텍스트에서는 조회할 때마다 같은 객체가 반환된다.
8. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 예외가 발생함
📌 프록시 객체의 초기화
- Client는 getName()을 호출
- 생성된 프록시 객체는 영속성 컨텍스트에 초기화 요청
- 영속성 컨텍스트는 실제 엔티티를 DB에 조회
- 영속성 컨텍스트는 조회된 실제 엔티티를 생성
- 프록시 객체는 갖고 있던 참조(target)를 통해 실제 엔티티와 연결하여 getName() 결과를 얻음
🔎 프록시 확인
EntityManagerFactory를 통해 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
em.getReference()로 생성되는 프록시 객체도 영속성 컨텍스트에서 관리한다.
📌 프록시를 사용하는 이유
프록시는 원본 객체를 위임받은 가벼운 객체이기 때문에 메모리나 성능상 이점을 갖고 있다.
먼저, 지연 로딩을 생각해 볼 수 있다. 지연 로딩은 단독 조회를 최적화하기 위해 불필요한 조인을 하지 않고 연관 엔티티를 프록시로 가져온다. 그래서 조인으로 인한
성능 저하를 막을 수 있고, 실제 엔티티 대신 프록시가 들어오게 되므로 메모리 상 이점도 있다.
지연 로딩이 아니더라도 연관된 엔티티를 사용하지 않는 화면이 있을 때, 프록시를 사용하는 것이 효과를 볼 수 있다. 사용하지 않는 연관 엔티티일 경우, 실제 엔티티가
아니라 빈 껍데기인 프록시를 갖고 있으면 위에 언급한 이점을 가질 수 있기 때문이다.
Reference:
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard