
한짤 요약(이 아닙니다)
프록시를 공부하기 이전에 이전에 영속성 컨텍스트에서 엔티티를 가져와 사용하거나, DB에서 데이터를 가져와 객체로 사용했던
이전의 과정을 기억해보았다.
왜?
프록시는 이 부분에서 엔티티를 사용할 때 마다 데이터를 불러오고, 영속화시키고, 영속성 컨텍스트에서 찾아오고를 줄여주기 위한 녀석이다.
Proxy, 영어로 '대리인' 이라는 뜻이 있다.
말 그대로 엔티티에 대한 대리인의 역할을 한다.
대리인, 혹은 복제품, 혹은 가짜라고 다른 글들에서 언급이 되어있다. 그 말대로, 프록시는 원본 객체가 아닌 원본에 대한 복제품을 가져오는 것이다.
가져오는 방법은, EntityManager.find()를 사용하는 것이 아닌, EntityManager.getReference()를 사용하는 것이다.
이 때 1차 캐시에 찾아오려는 객체가 없는 경우, 프록시 객체를 조회한다.
이렇게 임시로 객체를 불러오고, 데이터가 필요할 때 쿼리문이 나가며 데이터를 가져온다.
예를 들어 아이디가 1인 Member를 찾으려 할 때, getReference를 사용했다고 생각해보자.
이 때 class이름은 Member의 프록시, id = 1이다.

실제로 잘 나온다.
이후에 멤버의 인스턴스를 불러와 사용할 때 쿼리문으로 select해온다.
프록시는 원본 객체의 '대리' 혹은 '가짜'이다. 원본과 구조는 같지만, 속은 비어있는 상태이다.
여기에 다른 인스턴스가 하나 더 존재하는데,

Entity target이다. 어느 엔티티의 프록시인지가 정해져 있지 않기 때문에 초기엔 null로 되어있다.
이 프록시는 실제 클래스를 상속받아 만들어진다. 겉 모양은 동일해지고, Entity target은 초기화된다.
이후 동작되는 방식을 정리해보자면,

- 클라이언트가 프록시에게 getName을 요청한다.
- 프록시 객체가 영속성 컨텍스트에게 엔티티 초기화를 요청한다.
- 영속성 컨텍스트가 DB에서 데이터를 조회한다.
- 데이터를 받아온 영속성 컨텍스트가 원본 엔티티를 만든다.
- 생성한 엔티티에서 getName메서드를 호출에 데이터를 반환받는다.
이렇게 동작되는 프록시에도 몇가지 특징이 존재한다.
- 프록시 객체는 처음 사용할 때 한번만 초기화된다.
같은 동작을 몇번 반복해도 초기화되지 않는다. - 프록시를 초기화해도 실제 엔티티를 바꾸는 것이 아니다.
접근만 해오는 것이다. 결국 데이터를 수정할 때에는 프록시가 아닌 원본 객체를 가져온 상태여야 한다. - 프록시 객체와 원본 객체를 .eqauls하면 다르게 나온다. class가 다르기 때문이다.
만약 프록시 객체의 인스턴스와 원본 인스턴스를 비교하려면 인스턴스를 불러와 비교를 하거나
Instance of로 클래스 속성을 비교하는게 맞다. - 영속성 컨텍스트에 해당 엔티티가 있다면, 프록시가 아닌 원본 객체를 불러온다.
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태에서 프록시를 초기화하면 예외가 발생될 수 있다.
getReference를 이용하면 프록시로 객체를 불러온다. 이 객체는 영속성 컨텍스트에 존재하는데, 영속성 컨텍스트에서 해당 데이터를 제거하고 다시 호출할 경우, LazyInitializationException이 발생한다.
영속성 컨텍스트에서 프록시를 초기화해야하는데, 영속성 컨텍스트에서 프록시가 관리되지 못한다는 뜻이다.
4번에 대한 부연설명을 조금 더 붙이자면,

이 상태에서는 Proxy가 아닌 원본 객체가 와있다.
프록시를 한번 호출 한 이후에, em.find로 다시 데이터를 찾으려 해도 프록시를 가져와 반환시킨다.
이렇게 되었을 때 문제는, 프록시를 수정해도 원본 객체가 수정되지 않기 때문에, 꼬일 위험이 있다.
즉, 프록시가 먼저 호출되었으면 프록시로만 오게되고, 원본 객체를 불러오면 원본 객체만 불러온다.
프록시 사용을 원할하게 해주는 메서드들도 존재한다.
| EntityManagerFactory. getPersistenceUnitUtil().isLoaded(Object entity) |
프록시 인스턴스의 초기화 여부 확인. 즉 인스턴스가 영속성 컨텍스트로 인해 초기화 되었는지 확인해주는 역할을 한다. |
| entity.getClass().getName() | 프록시 클래스를 확인한다. |
| Hibernate.initialize(entity) | 프록시를 강제로 초기화시켜준다. 이는 하이버네이트 기준이며 JPA는 정형화된 강제 초기화법이 존재하지는 않는다. |
사실 이 프록시를 배우는 이유는 두가지 이다.
첫 번째로는 엔티티를 사용하는 매 순간 원본 객체를 가져오면 DB에 쿼리 이동량이 늘어나며, 최적화가 되지 않을 수 있다.
필요할 때가 되면 데이터를 가져오는 식으로 조정할 수 있게 된다.
두 번째로는 이렇게 데이터를 프록시로 불러오고, 필요할 때 원본 객체를 가져오는 식으로 지연로딩을 할 수 있다. 즉시로딩보다 효율적인 부분도 있다.
사실 이렇게 말해도 이해하기 어렵기도 하니, 즉시로딩과 지연로딩을 빨리 공부해야겠다.
'스프링 공부 > JPA' 카테고리의 다른 글
| [JPA] 18. 값타입이란거...들어보긴 했었나...? (0) | 2023.01.17 |
|---|---|
| [JPA] 17. 로딩(미안하다 이거보여주려고 어그로끌었다.) (0) | 2022.12.23 |
| [JPA] 15. 짜잔! 사실 이건 상속관계 매핑은 아닙니다. @MappedSuperclass (0) | 2022.11.29 |
| [JPA] 14. 객체는 원래 있는건데 DB에는 원래 없는 거 = 상속관계 (0) | 2022.11.27 |
| [JPA] 13. 다대다 매핑(이걸 매핑이라 부를까....?) (0) | 2022.11.03 |