-
JPA Hibernate ProxySpring 2022. 9. 13. 17:39
오늘도 열심히 사이드 프로젝트를 진행하던 중
동행모집 엔티티의 속한 멤버 중 요청 유저가 속해있는지 검사하는 걸 진행하던 중
의도와는 다르게 진행하는 걸 확인하게 된다.
동행모집 참가서 에서 불러온 Gathering 엔티티 안에 있는 GatheringMember 엔티티 데이터가 분명히 있는데도
불구하고 호출해서 확인해보면 의도와는 다르게 진행해서
디버그 모드로 해당 데이터들을 확인해보니
다음 스샷처럼 되어 있는걸 알 수 있었다.
gathering 과 requestUser 모두 데이터는 있을텐데
내부 데이터들이 모두 다 null 로 되어 있다.
그리고, 해당 변수의 처음 정보를 보면
Gathering$HibernateProxy~~~~
User$HibernateProxy~~~~
이렇게 되어 있는 것을 알 수 있다.
관련된 내용을 설명하기 전에 해당 엔티티에서 두 부분이 어떻게 쓰고 있는지 먼저 확인한다.
@Entity public class GatheringRequest extends DefaultEntity { @ManyToOne(fetch = LAZY) @JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false) private User requestUser; @JoinColumn(name = "gathering_id", referencedColumnName = "gathering_id", nullable = false) @ManyToOne(fetch = LAZY) private Gathering gathering; }
두 군데 다 연관 관계가 @ManyToOne(fetch = LAZY) 로 되어있다.
fetch 타입이 LAZY로 되어 있으니,
EAGER와는 다르게 지연으로 가져와서 바로 사용할 수가 없다.
성격 급한 한국인은 문제부터 수정하고
관련 설명 하는게 맞으므로
빠르게 대안부터 간다!
기존 소스를
final User requestUser = gatheringRequest.getRequestUser(); final Gathering gathering = gatheringRequest.getGathering();
다음과 같이 수정한다.
final User requestUser = gatheringRequest.getRequestUser(); final User unproxyRequestUser = Hibernate.unproxy(requestUser, User.class); Gathering gathering = gatheringRequest.getGathering(); final Gathering unproxyGathering = Hibernate.unproxy(gathering, Gathering.class);
수정 후 다시 디버그로 돌려보면
다음과 같은 결과를 볼 수 있다.
gathering 과 requestUser 는 기존과 같이 null 로만 있지만
unproxyRequestUser 와 unproxyGathering 은 데이터가 존재하는 것이 확인되며,
처음 정보 역시 HibernateProxy ~~ 부분이 없이 바로 엔티티와 아이디로 확인이 된다.
프록시
그럼 이 프록시에 대한 개념을 알아보자
위에서 얘기한 GatheringRequest 엔티티를 조회할때,
requestUser, gathering 이 사용하는 경우도 있지만
비즈니스 로직에서 사용하지 않는 경우가 있는데, 사용을 하지 않는데 다 조회하게 되면 낭비가 발생하게 된다.
JPA에서는 이 낭비를 하지 않기 위해 지연로딩(lazy loading)과 프록시 라는 개념으로 해결한다.
프록시란,
실제 엔티티 객체 대신에 사용되는 객체 로서 위 사진에서 보는 것처럼 실제 엔티티 클래스와 상속 관계 및 위임 관계에 있다. 그래서 프록시 객체는 생긴 모양, 구조는 실제와 똑같다.
똑같은 이유는 위 설명처럼 실제 엔티티 클래스로부터 상속 받아서 만들어지기 때문이다.(하이버네이트가 상속해서 생성)
위에서 한번 나왔던
이 내용을 보더라도
위와 아래의 값은 다르더라도 생긴 형태는 동일한 것을 알 수 있다.
프록시 객체에서는 원본이 어떤것인지 알 수 있는 target 엔티티를 보관해서
이 target 엔티티를 통해 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 초기화
위 스샷에 덧붙여 프록시 초기화란,
프록시 객체가 참조하는 실제 엔티티가 persistence context에 생성되어 있지 않을 때, persistence context에 실제 엔티티 생성을 요청하고 생성된 실제 엔티티를 프록시 객체의 참조 변수에 할당하는 과정을 말한다.
타 블로그에서 가져온 이미지 인데, 이 이미지 순서에 따르면
1. book 엔티티의 title를 조회하는 함수 호출하면,
2. book 엔티티의 데이터를 조회하지만 데이터가 '점선표시 그림' 처럼 비어있는 것을 확인
3. 데이터가 없으니 디비에 초기화 요청을 보냄
4. 디비에서 가져온 book 엔티티 데이터를 persistence context 에 1차 캐시로 저장
5. 이후, 저장된 엔티티에서 book 의 title 정보를 반환
이러한 순서로 진행한다고 한다.
출처: https://victorydntmd.tistory.com/210
'Spring' 카테고리의 다른 글
@Transactional 에 대한 고찰 (0) 2023.03.26 Spring API 공통 response 포맷 개발하기 (1) 2022.07.10