JPA 에서의 '프록시(Proxy)' 는 기존에 알고 있던, 클라이언트와 서버 사이에 위치하는 '프록시 서버(Proxy Server)' 와는 개념이 전혀 달랐다.
느낌은 비슷했는데, "사용자가 리소스에 직접 접근하지 않고, 대신 접근해주는 중간 매개체" 라는 점이 비슷했다.
"프록시 서버 (Proxy Server)" 는 클라이언트가 직접 서버와 통신하지 않고, 프록시 서버를 통해 서버와 통신하는 것이고,
"JPA 의 프록시 (Proxy)" 는 실제 엔티티 객체를 조회하는 대신, 지연로딩을 하는 가짜 (프록시) 엔티티 객체를 조회하는 것이다.
1. 프록시의 필요성
※ 아래와 같은 객체가 존재할때, Member를 조회할때, Team도 함께 조회해야 하는가?
Member의 데이터만 사용하는데, Team 까지 조회하는 것은 성능상 낭비이다.
* 조회할 데이터가 아주 많다면?
* Member가 Team뿐만 아니라 다른 객체들도 필드값으로 갖는다면?
이러한 문제를 해결하는 것은 시급한 문제이다.
이를 위해 프록시(Proxy)가 필요하다.
프록시는 굳이 지금 사용할지 안할지 모르는 불확실한 조회를 하는 것은 성능상 불리하다.
따라서 해당 객체가 필요할때, (조회 요청이 있는 경우) 그때 조회를 하겠다.
프록시는 위와 같은 목적으로 만들어졌다.
2. 프록시 구성 / 특징
1. 프록시 객체는 해당 실제 엔티티 클래스를 상속받아 만들어진다.
-> 실제 엔티티와 겉모양이 같다.
-> 따라서, 사용자 입장에서 프록시 객체인지 실제 엔티티 객체인지 구분할 필요가 없이 사용 가능
2. 프록시 객체의 'target' 은 객체의 참조를 보관한다.
3. 프록시 객체를 호출하면 프록시 객체는 실제 엔티티 객체의 메서드를 호출한다.
3. 프록시 객체의 초기화
※ 전제 : 현재 영속성 컨텍스트에는 "Member Proxy" 프록시 객체 하나만 올려져있다.
(1) "1. getName()" : Client 가 프록시 객체에게 값 조회를 요청
(2) 프록시 객체는 현재 영속성 컨텍스트에 Member 실제 엔티티가 없기 때문에 영속성 컨텍스트에 "2. 초기화 요청"을 보낸다.
(3) 영속성 컨텍스트는 "3. DB 조회" 를 위해 SQL 쿼리를 보낸다.
(4) "4. 실제 Entity 생성" 이 이루어진다.
(4-1) "Member Proxy" 프록시 객체 target 은 실제 Entity 의 참조값을 가진다.
(5) "Member Proxy" 프록시 객체는 "5. target.getName()" 을 통해 실제 Entity 에서 "Name" 필드 값을 가져와 Client에게 반환한다.
** 코드로 알아보기 **
1. Client ----값 조회 요청(.getUsername())----> 'refMember' 프록시 객체
2. 'refMember' 프록시 객체 ----초기화 요청-----> 영속성 컨텍스트
3. 영속성 컨텍스트 ----조회 SQL 쿼리----> DB
4. DB ----조회 데이터 반환----> 영속성 컨텍스트
5. 영속성 컨텍스트 ----> 실제 엔티티 생성
6. 'refMember' 프록시 객체 의 target : '실제 엔티티 참조값' 으로 설정
7. 'refMember' 프록시 객체 ----target.getUsername()----> 실제 엔티티
8. 실제 엔티티 ----username 반환----> 'refMember' 프록시 객체
9. 'refMember' 프록시 객체 ----username 반환----> Client
※ 프록시 초기화 정의
(최초) 실제 값 요청이 왔을때, 영속성 컨텍스트를 통해 DB 조회 하여 실제 Entity를 생성하는 과정
프록시 초기화 특징
1. 프록시 객체는 처음 사용할 때 한번만 초기화
-> 영속성 컨텍스트에 해당 실제 엔티티가 없는 상황에서 프록시 객체에서 값 요청이 왔을때, 프록시 초기화가 일어남.
2. 프록시 객체를 초기화 하면, 프록시 객체가 실제 엔티티로 변하는 것이 아님
-> 프록시 객체를 통해 실제 엔티티로 접근 가능
3. 프록시 객체는 원본 엔티티를 상속 받아 만들어짐
-> 타입 비교시 == 가 아닌 instance of를 사용
-> 프록시 객체는 실제 엔티티와 겉모습이 동일하기 때문에 사용자가 프록시 객체인지 실제 엔티티인지 구분하기 어려움. 따라서 ==을 무분별하게 사용한다면, 예상치 못한 오류가 발생 하기 때문에 instance of를 기본으로 사용하도록 하자.
4. 영속성 컨텍스트에 찾는 엔티티가 있으면, em.getReference()를 호출해도 실제 엔티티 반환
-> 아래 자세히 설명...
5. 프록시 객체가 영속성 컨텍스트의 도움/관리 를 받을 수 없는 준영속 상태일 때, 프록시를 초기화 하면 문제 발생
-> 프록시 객체의 초기화 요청은 영속성 컨텍스트를 통해서 이루어지기 때문.
※ "4. 영속성 컨텍스트에 찾는 엔티티가 있으면, em.getReference()를 호출해도 실제 엔티티 반환" 에 대한 설명
1. 영속성 컨텍스트에 찾는 엔티티가 이미 있는 상황에서, getReference()를 호출한다면, 프록시 객체로 호출될까?
1. em.find() 를 이용해 'findMember' 를 호출
2. em.getReference() 를 이용해 'refMember' 를 호출
3. 'findMember' 와 'refMember' 간의 타입 비교를 '==' 을 이용
위와 같은 순서로 진행 되었다.
2번 순서에서, em.getReference() 로 호출한 객체 'refMember' 가 프록시 객체로 생성되는 것이 아닌 진짜 객체 (class hellojpa.Member)로 호출된 것을 볼 수 있다.
이러한 일이 일어난 이유는 "영속성 컨텍스트에 진짜 엔티티가 존재" 하기 때문이다.
JPA 입장에서 이미 영속성 컨텍스트에 진짜 엔티티가 존재하는데, em.getReference() 로 호출한다고 해서 굳이 프록시 객체로 생성할 이점이 없기 때문이다.
프록시 객체의 장점인 "실제 값 요청이 올 경우에 DB로 부터 값을 불러온다." 라는 이점이 사라졌다.
이미 영속성 컨텍스트에 진짜 엔티티가 존재하기 때문에, 모든 값이 다 불러와져 있는 상황이다.
=> 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
2. em.getReference() 로 프록시 객체가 먼저 호출된 경우, em.find() 를 호출한다면, 진짜 엔티티가 호출될까?
1. em.getReference() 를 이용해 'refMember' 를 호출
2. em.find() 를 이용해 'findMember' 를 호출
3. 'findMember' 와 'refMember' 간의 타입 비교를 '==' 을 이용
위와 같은 순서로 진행 되었다.
여기서는 이상한 점이 발견되었다.
1번으로 refMember로 프록시 객체가 호출되었다.
2번으로 진짜 엔티티를 호출하기 위해 DB로 SELECT 쿼리를 보냈다.
그럼에도 불구하고 em.find()로 굳이 굳이 프록시 객체를 호출한 것을 볼 수 있다.
도대체 왜 DB로 SELECT 쿼리 까지 보내 영속성 컨텍스트에 진짜 엔티티를 가져다 놓는 수고를 하고서도 em.find()에서 프록시 객체를 생성한 것일까?
그 이유는 다음과 같다.
JPA는 한 트랜잭션 안에서 "동일성"을 보장해준다.
즉, 한 트랜잭션 안에서 같은 것을 여러번 호출하더라도, 항상 같은 객체가 호출되도록 한다.
이 대전제를 지키기 위해, 같은 객체가 한번 프록시 객체로 호출되었다면, 해당 트랜잭션이 끝날 때 까지 호출시 프록시 객체를 반환해주는 것이다.
(+) 설령 프록시 객체를 생성한다 해도 성능상의 불리한점은 없다.
프록시 객체 자체가 진짜 엔티티 클래스를 상속 받아 만들어지기 때문이다.)
=> 한 트랜잭션 안에서 동일성을 위해, 처음 호출된 객체가 프록시 객체라면 em.find()를 통해 호출하더라도 프록시 객체가 생성
3. em.getReference() -> 영속성 컨텍스트 관리 해제(detach / clear) -> em.find()
1. refMember(프록시 객체)를 생성
2. refMember 는 영속성 컨텍스트의 관리를 받지 못함
3. em.find() 로 진짜 엔티티 객체를 불러옴.
영속성 컨텍스트의 관리를 받지 못하기 때문에,
프록시 객체를 생성하는 것이 아닌 진짜 엔티티 객체를 생성하는 것을 볼 수 있다.
※ " 5. 프록시 객체가 영속성 컨텍스트의 도움/관리 를 받을 수 없는 준영속 상태일 때, 프록시를 초기화 하면 문제 발생" 에 대한 설명
em.detach(refMember)를 통해 프록시 객체를 영속성 컨텍스트의 관리를 받을 수 없게 제외시킨 후,
값 조회(.getUsername())을 하면, 프록시 객체는 값 조회 요청이 오면 영속성 컨텍스트에게 '초기화 요청'을 해야 한다.
그러나 관리 대상이 아니기 때문에 'LazyInitializationException' 이 발생하고, "no Session"을 통해 해당 프록시 객체는 영속성 컨텍스트의 관리 대상이 아님을 알 수 있다.
+) 프록시 초기화 여부 확인
'Spring > JPA' 카테고리의 다른 글
[JPA - 13 - JPQL] 페치 조인 (Fetch Join) (0) | 2023.12.15 |
---|---|
[JPA - 10] 즉시로딩 / 지연로딩 (0) | 2023.12.07 |
[JPA - 8] 상속관계 매핑 / @MappedSuperclass (0) | 2023.12.02 |
[JPA - 7] 다양한 연관관계 매핑 (0) | 2023.12.01 |
[JPA - 6] 연관관계 매핑 (양방향 연관관계 / 연관관계의 주인 - 중점) (0) | 2023.11.26 |