본문 바로가기
JAVA & SPRING/JPA

JPA 기본편(프록시)

by 눈오는1월 2024. 3. 4.
728x90

본 내용은 인프런 김영한 강사님 JPA 기본 편 강의를 듣고 정리한 내용입니다.

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런

현업에서 실제로 JPA로 개발을 하고 있습니다. 그런 입장에서보면 지금 작성하고 있는 코드들이 어떻게 작동하는지 이해하는데 큰 도움을 주는 강의입니다. 다음은 제가 느낀 이 강의의 장점들

www.inflearn.com

 

 

만약 Member를 조회할 때 Team도 조회를 해야 하는지에 대해 생각을 해보자 

만약 Team 조회가 필요 없을 경우 불필요하게 Team을 쿼리를 통해 가져오게 되는 경우가 존재한다. 이런 경우 프록시 + 지연로딩을 통해 해당 문제를 해결할 수 있다.

 

프록시란?

정말 쉽게 말하면 가짜 엔티티를 의미한다.

예시 코드를 보면서 확인해 보자

 

em.find 실행 코드
em.find를 호출했을 때 결과

em.find를 통해서 객체를 찾으려고 하면 영속성 컨텍스트에 아무것도 존재하지 않을 때, 당연히 select 쿼리가 나가는 것을 볼 수 있다. em.find를 하면 디비에 해당 엔티티를 가져오기 때문이다.

 

아래 코드는 em.find가 아닌 em.getReference를 실행시킨 코드와 결과이다.

em.getReference 호출해을 때 결과

위 코드와 결과를 보면 em.getReference를 호출했을 때, select 쿼리문이 발생하지 않는다. 

이러한 이유는 프록시로 인해 발생하는 현상이다.

 

 

getReference를 호출하게 되면 디비에서 엔티티를 가져오는 것이 아닌 프록시 객체를 생성한다. 이후 findMember를 사용하게 되면 그때 디비에 쿼리를 보내 프록시 객체가 디비에 가져온 엔티티를 참조하게 되는 것이다.

 

 

결과를 자세히 보면, id가 출력되고 나서 select 쿼리가 나가게 되는데 이건 id는 이미 가지고 있으니 select 쿼리문을 보내는 것이 아니고 이후에 해당 정보를 가져오기 위해 select 쿼리가 발생하는 것이다. 

 

이러한 프록시의 특징

  • 실제 클래스를 상속받아서 만들어졌다.
  • 실케 클래스와 겉모양은 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다(이론상)

또한 위에서 언급드린 것처럼 프록시 객체는 실제 객체의 참조를 보관했다가 프록시 객체가 호출되면 실제 객체의 메서드가 호출된다.

위 코드에서 getUserName처럼 실제 객체의 메서드가 호출될 때 이때를 프록시 객체의 초기화 라고 한다.

프록시 객체 초기화 과정

한번 초기화 한 이후에는 영속성 컨텍스를 거치지 않는다.

 

프록시 객체의 중요한 특징

1. 프록시 객체는 처음 사용할 때 한 번만 초기화한다.

2. 프록시 객체를 초기화할 때, 프록시 객체가 엔티티로 바뀌는 것이 아닌 프록시 객체를 통해 엔티티에 접근하는 것이다.

3. 프록시 객체는 원본객체를 상속받기 때문에 타입 체크 시 주의해야 한다.(== 비교 사용 금지 instacnce of를 사용해야 한다)

4. 영속성 컨텍스트에 엔티티가 존재하면 em.getReferece()를 호출해도 엔티티로 반환한다.

5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속일 때는 프록시를 초기화하면 문제가 발생한다.

 

3번 예제
3번 예제 결과

위처럼 코드를 돌렸을 경에 프록시랑 엔티티랑 다른 것이므로 == 비교를 하면 false가 발생한다.

4번 예제
4번 예제 결과

위처럼 findMember를 가져온이후에 프록시로 같은 엔티티를 참조할 경우 프록시가 가져오는 것이 아닌 엔티티가 가져와진다. 이러한 이유는 JPA는 같은 트랜젝션에서 같은 것을 보장을 해주기 때문이다. 

1차 캐시에서 가져온 것을 프록시로 참조하는 것은 의미가 없기 때문에 영속성 컨텍스트 내에 엔티티가 존재하면 프록시가 아닌 엔티티를 가져오는 것이다.

 

그럼 반대 상황에 대해서도 생각을 해보자

 

반대로 프록시를 먼저 하고 엔티티를 find로 가져오게 되더라도 같은 트랜젝션에서는 같은 것을 항상 보장해 주기 때문에 find로 가져온 것이 프록시로 참조되어 있는 것을 알 수 있다.

728x90