![[JPA] 프록시 객체를 통해 필요한 엔티티만 조회하기, getReference() (old)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqZmg8%2Fbtst5vElKHu%2F6kSQFaoR1fEzbYFkEnnhok%2Fimg.png)
DB에서 특정 엔티티를 조회하면 연관관계에 있는 엔티티 또한 같이 조회됩니다. 연관관계에 있는 엔티티도 조회하기 위해서는 DB에 join과 함께 쿼리문이 나가게 됩니다. 특정 상황에서는 굳이 연관관계에 있는 엔티티가 필요로 없는 상황이 있을 수도 있습니다. 이때 연관관계에 있는 엔티티까지 DB에서 조회하는 것은 효율적이지 않습니다.
JPA는 이런 문제를 해결하기 위해 엔티티가 실제 사용될 때까지 DB에서 조회를 지연시키는 방법을 제공합니다. 이를 지연로딩이라고 합니다.
기존 조회 - 연관관계에 있는 엔티티까지 조회
EntityManager의 find() 메서드를 사용하여 엔티티를 조회합니다. clear()로 인해 영속성 컨텍스트가 초기화되었으므로 DB로부터 엔티티를 조회해서 1차 캐시에 저장합니다.
출력을 보면 Member 클래스 정보를 출력하고 있습니다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setTeam(team);
member.setName("memberA");
em.persist(member);
em.flush(); // Insert 쿼리문 DB로 보냄
em.clear(); // 영속성 컨텍스트 초기화
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
// 출력
findMember.getClass() = class jpay.jpastudy.Member
프록시 객체 조회 - DB로부터 조회 지연
EntityManager의 getReference() 메서드를 사용하여 프록시 객체를 조회합니다. 프록시 객체란 실제 엔티티 대신에 DB 조회를 지연할 수 있는 가짜 객체를 의미합니다. 프록시 객체는 실제로 사용될 때 DB를 조회하여 실제 엔티티 객체를 생성합니다. (이를 프록시 객체의 초기화라고 합니다)
출력 부분을 보면 기존 조회와 다른 클래스 정보를 출력하고 있습니다. HibernateProxy라는 정보가 붙었는데, 이를 통해 해당 객체가 프록시 객체임을 알 수 있습니다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setTeam(team);
member.setName("memberA");
em.persist(member);
em.flush();
em.clear();
Member proxyMember = em.getReference(Member.class, member.getId());
System.out.println("proxyMember.getClass() = " + proxyMember.getClass());
// 출력
proxyMember.getClass() = class jpay.jpastudy.Member$HibernateProxy$1iEQ2I41
프록시 객체를 초기화를 하려면 어떻게 해야 할까요? 위에서 말했듯이 실제 엔티티가 사용되어야 DB에서 조회한다고 하였습니다. 실제 엔티티 객체의 name을 반환하는 메서드를 호출해봅시다.
(참고로 id 조회는 실제 엔티티 사용에 해당하지 않습니다. 프록시 객체(em.getReference) 또한 식별자로 조회하는 것이기 때문에 프록시 객체는 이미 식별자 값을 가지고 있기 때문입니다.)
proxyMember.getName();
실제 엔티티 객체를 사용했으니 proxyMember 객체는 실제 엔티티 객체로 바뀌는 것인가?라고 생각한다면 틀렸습니다. 클래스 정보를 출력하면 여전히 프록시 객체임을 보여주고 있습니다.
프록시 객체가 초기화되면 프록시 객체를 통해 실제 엔티티에 접근할 수 있을 뿐, 프록시 객체가 실제 엔티티로 바뀌는 것은 아닙니다.
영속성 컨텍스트 조회
EntityManger의 find(), getReference()를 통해 엔티티를 조회하여 얻은 엔티티 객체는 영속성 컨텍스트에 저장됩니다. 실제 엔티티를 조회했다면 실제 엔티티가 영속성 컨텍스트에 저장되고, 프록시 객체를 조회했다면 프록시 객체가 영속성 컨텍스트에 저장됩니다.
실제 엔티티 조회 후 프록시 객체 조회
DB를 통해 실제 엔티티 객체를 조회하여 영속성 컨텍스트에 저장합니다. 클래스 정보를 출력하면 당연히 실제 엔티티의 클래스 정보가 출력됩니다. 근데 DB에서 조회를 지연하기 위해 프록시 객체를 조회해도 실제 엔티티의 클래스 정보가 출력됩니다. 분명히 위에서 프록시 객체를 조회했을 때 실제 엔티티가 아닌 프록시 객체의 클래스 정보가 출력되었는데, 이번엔 실제 엔티티의 클랫 정보가 출력되었습니다.
그 이유는 실제 엔티티를 조회하든 프록시 객체를 조회하든 먼저 영속성 컨텍스트에서 찾기 때문입니다. 영속성 컨텍스트에 조회하고자 하는 식별자의 엔티티가 저장되어 있다면 DB에서 조회한 엔티티를 반환하지 않고 영속성 컨텍스트에 저장된 엔티티를 반환합니다. 따라서 em.find()를 통해 얻은 실제 엔티티를 영속성 컨텍스트에 저장하고 이후에 em.getReference()를 통해 영속성 컨텍스트에 저장된 실제 엔티티를 가져오게 됩니다.
Member findMember = em.find(Member.class, member.getId());
Member proxyMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("proxyMember.getClass() = " + proxyMember.getClass());
// 출력
findMember.getClass() = class jpay.jpastudy.Member
proxyMember.getClass() = class jpay.jpastudy.Member
프록시 객체 조회 후 실제 엔티티 조회
em.getReference()를 통해 얻은 프록시 객체를 영속성 컨텍스트에 저장하고 이후에 em.find()를 통해 영속성 컨텍스트에 저장된 프록시 객체를 가져오게 됩니다.
Member proxyMember = em.getReference(Member.class, member.getId());
Member findMember = em.find(Member.class, member.getId());
System.out.println("proxyMember.getClass() = " + proxyMember.getClass());
System.out.println("findMember.getClass() = " + findMember.getClass());
// 출력
proxyMember.getClass() = class jpay.jpastudy.Member$HibernateProxy$95t71s1J
findMember.getClass() = class jpay.jpastudy.Member$HibernateProxy$95t71s1J
결론
..