☕️자바 𝗝𝗔𝗩𝗔

[JPA] 프록시란? (지연로딩)

비타민찌 2022. 9. 22. 12:48
728x90

회원 정보만 출력하는 비지니스 로직과 회원과 팀 정보를 출력하는 비지니스 로직을 비교해보자.

 

[회원 정보만 출력]

public String printUser(String memberId) {
    Member member = em.find(Member.class, memberId);

    System.out.println("회원 이름 : " + member.getUserName());
}

 

[회원과 팀 정보를 출력]

public void printUserAndTeam(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();

    System.out.println("회원 이름 : " + member.getUserName());
    System.out.println("소속팀 : " + team.getName());
}

 

음..  회원 정보만 사용하는 곳에서 연관된 팀 엔티티까지 데이타베이스에서 조회해 두는 것은 효율적이지 않은 것 같다. JPA는 엔티티가 실제 사용될 때까지 데이타베이스 조회를 지연하는 방법을 제공하는데 이를 지연 로딩이라고 한다.

 

지연 로딩을 사용하기 위해서는 가짜 객체 '프록시' 라는 것이 필요한데..

// 엔티티 직접 조회 - 영속성 컨텍스트에 없으면 DB 조회
Member member = em.find(Member.class, 100L);

// 엔티티를 실제 사용하는 시점까지 미루는 프록시 객체
Member member = em.getReference(Member.class, 100L);

 

JPA 프록시란?

객체가 객체 그래프로 연관된 객체들을 탐색할때, 데이터베이스에 저장되어 있는 객체를 처음부터 데이터베이스에서 조회하는 것이 아니라

실제 사용하는 시점에 데이터베이스에서 조회할 수 있게 해주는 기술이다.

물론 자주 함께 사용하는 객체들은 조인으로 함께 조회하는 것이 좋은데, JPA는 즉시로딩, 지연로딩 이라는 방법으로 두 방식 다 지원한다.

또한 연관된 객체를 함께 저장하거나 삭제할 수 있는 영속성 전이고아 객체 제거라는 기능도 제공한다.

 

프록시 객체는 실제 클래스를 상속 받아서 만들어진 겉모양만 똑같은 가짜 객체다. 그리고 target이라고 해서 실제 엔티티 객체의 참조를 저장할 수 있다. 처음에는 target에 null이 들어가 있어서 아무것도 연결되지 않은 상태다. 프록시 객체가 동작하는 것을 코드를 보면서 살펴보자.

member에는 프록시 객체가 들어간다.

그 뒤에 (1)getName을 호출하면 프록시객체는 그런 함수가 없기 때문에

영속성 컨텍스트에 (2)초기화를 요청. 그러면

(3)영속성 컨텍스트는 DB에서 실제 그 Member객체를 찾아서

(4)실제 엔티티 객체와 프록시 객체를 연결해준다.

그러면 프록시 객체는 (5)member.getName()을 target.getName()으로 처리할 수 있게 된다.

 

주의해야할 것은 프록시 객체와 실제 엔티티 객체를 이어주는 초기화 과정은 한번만 일어나고 그 뒤로는 target에 들어온 엔티티에 접근해서 함수들을 호출해 나간다는 점이다. 따라서 초기화 후에 프록시 객체가 실제 엔티티 객체로 대체되는 것이 아니라, 프록시 객체가 실제 엔티티 객체로 접근할 수 있게 된 것이다. 초기화 과정은 영속성 컨텍스트를 매개해서 이뤄지기 때문에 영속성 프록시 객체가 준영속 상태라면 초기화를 요청해도 오류가 난다.

 

// 프록시객체 : 실제 클래스와 겉모양이 같음
Member findMember = em.getReference(Member.class, member.getId()); // 쿼리 안나감 

System.out.println("findMember.getId = " + findMember.getId());

// 쓰일 때 쿼리날림 (초기화)
System.out.println("findMember.getUsername = " + findMember.getUsername());
           
// 초기화 되서 초기화 요청 안함
System.out.println("findMember.getUsername = " + findMember.getUsername());

 

여기서 findMember.getId()에서 초기화가 되지 않는 이유는 em.getReference때에 member.getId()로 id값을 넘겼기 때문에 프록시객체가 id값을 저장 했기 때문이다. 따라서 getUsername시에 쿼리가 날라가며 최초로 초기화가 되고 그뒤에 getUsername을 한번더 호출할 때는 이미 target에 실제 객체가 연결되었기 때문에 쿼리가 날아가지 않는다. findMember.getUsername()처럼 초기화를 시킬 수 도있지만 org.hibernate.Hibernate.initialize(entity); 로 강제 초기화 할 수도 있다.

 

JPA 프록시 객체의 특징 정리

- 처음 사용할 때 한번만 초기화.

- 프록시 객체가 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근 가능.

- 원본 엔티티를 상속받은 객체, 타입 체크 주의.

- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환.

참고:

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

728x90