프록시(Proxy)
2022. 5. 25. 13:50ㆍJava/JPA
프록시는 그 자체의 개념뿐만 아니라 즉시 로딩, 지연 로딩을 온전히 이해하기 위해 알아두어야 하는 개념입니다.
프록시를 왜 써야 하는지부터 차근차근 알아보도록 하겠습니다.
프록시를 왜 써야 하는가?
- Member를 조회할 때 Team도 함께 DB에서 조회해야 할까?
- Team을 사용하지 않는 경우 = Team까지 조회를 하는 것은 리소스의 손해이다
- 해결 : 프록시를 써서 지연로딩을 이용하면, 실제 필요할 때 Team을 DB에서 조회하여 리소스를 절약할 수 있다.
프록시
em.find() vs em.getReference()
em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
- DB에 쿼리가 안 나가는데 객체가 조회됨
프록시 객체의 초기화
// DB를 통해 실제 엔티티 객체 조회, 이 경우 Member와 Team을 Join하여 모두 조회함
Member member = em.find() :
// DB 조회를 미루는 가짜 엔티티 객체 조회(프록시), 이 경우 Member만 조회하며,
// 이 후 실제 해당 객체를 사용할 때 DB를 조회함
Member proxyMember = em.getReference() :
- 이때 proxyMember는 실제 Entity의 값을 확인할 수 없으며,
이를 위해선 아래 영속성 컨텍스트를 통한 초기화 과정이 필요함.
- 프록시 객체에 요청이 들어왔을 때(그림 1번) target이 없을 경우, 영속성 컨텍스트를 통해
DB를 조회해서(2~3번) 실제 Entity를 생성한다.(4번)
그 후 target에 연결해서 진짜 엔티티의 getName()반환한다.(5번) - 이 후 같은 객체에 요청을 할 경우, 기 조회된 Entity이기 때문에 다시 DB를 조회하지 않는다.
- 프록시 객체에 요청이 들어왔을 때(그림 1번) target이 없을 경우, 영속성 컨텍스트를 통해
💡 프록시 객체의 특징 💡
1. Proxy는 실제 클래스를 상속받아서 만들어져 기존 클래스와 겉모양이 같다.
- 사용하는 입장에서는 실제 객체와 프록시 객체와 구분하지 않고 사용 가능하다.
2. 프록시 객체는 실제 객체의 참조를 보관 (target)
3. 처음 사용할 때 한 번만 초기화하고, 이때 프록시 객체가 실제 Entity로 바뀌는 것은 아님
- 초기화되면 프록시 객체를 통해 실제 엔티티에 접근 가능
4. ❗Type 체크 시 주의❗ ⇒ 대신 instance of 사용
Member m1 = em.find(Member.class, member1.getId()); // 직접 조회
Member m2 = em.getReference(Member.class, member2.getId()); // 프록시 조회
if(m1.getClass() == m2.getClass()) // False
if(m2 instanceof Member) // True
5. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, getReference() 하여도 실제 엔티티 반환
반대의 경우도 마찬가지이다, 프록시로 처음 조회 했다면 그 후 em.find()해도 프록시 반환
// DB 직접 조회
Member m1 = em.find(Member.class, member1.getId());
// 영속성 컨텍스트 내 Entity 조회
Member m2 = em.getReference(Member.class, member1.getId());
// 같을까? 힌트 : JPA에서 인스턴스 비교 ==는 같은 영속성 컨텍스트 안에서 조회하면 항상 같다라고 나옴
if(m1 == m2) // True, 둘이 동일 클래스 (Member)
// 이유: 이미 1차캐시에 엔티티 있음 -> 이것을 가져오는 게 성능 최적화면에서도 좋음
// Proxy 조회
Member m1 = em.getReference(Member.class, member1.getId());
// DB 직접 조회, 클래스는 HibernateProxy로 반환
Member m2 = em.find(Member.class, member1.getId());
if(m1 == m2) // True, 둘이 동일 클래스 (HibernateProxy)
6. 영속성 컨텍스트의 도움을 받을 수 없는 ‘준영속 상태’ 일 때, 프록시 초기화 시 문제 발생
- (Transaction과 영속성컨텍스트의 시작과 끝을 주로 맞추므로) Transaction 끝날 때 이슈 발생
// 1. 정상 케이스
Member refMember = em.getReference(Member.class, member1.getId());
refMember.getUsername(); // 이 시점에서 실제 DB 조회 및 출력
// 2. 준영속 상태
Member refMember = em.getReference(Member.class, member1.getId());
em.detach(refMember); //또는 em.close() 또는 em.clear() 해도 같은 예외 발생
refMember.getUsername(); // Exception 발생
// 하이버네이트 예외발생 LazyInitializationException - No session
프록시 확인
1. 프록시 인스턴스의 초기화 여부 확인
- entityManagerFactory.getPersistenceUnitUtil().isLoaded(Object entity);
2. 프록시 클래스의 확인 방법
- entity.getClass().getName();
3. 프록시 강제 초기화 (비표준)
- org.hibernate.Hibernate.initialize(entity);
- 참고 : JPA 표준은 강제 초기화 없으므로, 객체 조회로 강제 호출(member.getName();)해야함
JPA기본편 스터디 함께한 Jarry, Matt 항상 감사합니다.
참고 :
https://www.inflearn.com/course/ORM-JPA-Basic
'Java > JPA' 카테고리의 다른 글
영속성 전이(CASCADE)와 고아 객체 (0) | 2022.05.25 |
---|---|
즉시 로딩과 지연 로딩 (0) | 2022.05.25 |
[API 개발 고급] 조회용 샘플 데이터 입력 (0) | 2022.05.24 |
[API 개발] 회원 등록, 수정, 조회 API (0) | 2022.05.24 |
❗주문 도메인 개발❗, 웹 계층 개발 (0) | 2022.05.23 |