BackEnd/JPA

[JPA] 프록시(Proxy) 기초 (2)

JJangGu 2022. 5. 18. 00:58

저번 글에 이어서 프록시에 대해서 더 알아보겠습니다. 

 

JPA에서 식별자로 엔티티를 조회할 때는 EntityManager.find() 를 사용합니다. 그런데 이렇게 조회하면 조회한 엔티티를 사용하든 사용하지 않든 데이터베이스를 조회하게 됩니다. 그런데 저번에 봤듯이 항상 연관된 객체들이 모두 필요한 것은 아니죠.

 

EntityManager.getReference() 를 사용하면 JPA 는 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않습니다. 바로 프록시 객체를 반환하죠.

 

 

위의 그림처럼 프록시 객체를 리턴해줍니다. 본격적으로 프록시 객체가 어떤건지 알아보겠습니다. 전에 작성한 글에서 프록시 서버, 리버스 프록시 와 같은 이야기도 했는데요, 여기서 다룰 프록시는 프록시 패턴에 관련되어 있습니다. 

 

프록시 클래스는 실제 클래스를 상속 받아서 만들어집니다. 그리고 실제 객체의 참조를 보관하고 있습니다. 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체에게 그 역할을 위임해서 실제 객체의 메소드가 호출되는 구조입니다. 

 

 

메소드를 호출하면 실제 객체의 메소드가 호출된다. 이 말은 곧, 실제로 사용될 때 실제 엔티티가 사용이 된다는 말입니다. 그리고 실제 엔티티 객체를 생성하면서 데이터베이스를 조회하게 되겠죠. 이것을 프록시 객체의 초기화라고 합니다.

 

책의 예시로 어떻게 동작하는지를 예상해보겠습니다.

 

Member member = em.getReference(Member.class, "id1");
member.getName();

 

위와 같이 코드를 실행했을때, 예상되는 프록시 클래스의 코드입니다. 

 

class MemberProxy extends Member {
    
    Member target = null; // 실제 엔티티 참조
    
    public String getName() {
        if (target == null) {
            // 초기화 요청
            // DB 조회
            // 실제 엔티티 생성 및 참조 보관
            this.target = ...;
        }
        
        // target.getName()
        return target.getName();
    }
}

 

getName() 을 호출하면, 프록시 객체의 초기화가 일어나면서 실제 target 클래스의 메소드를 호출하는 과정입니다. 

 

 

다시 차례를 살펴보면, 프록시 객체에 getName() 을 호출해서 실제 데이터를 조회하고자 합니다. 그러면 프록시 객체는 실제 엔티티가 생성되어 있지 않다면 영속성 컨텍스트에 엔티티 생성을 요청하고 이것이 아까 언급했던 '프록시 객체 초기화' 입니다.

 

영속성 컨텍스트가 데이터베이스를 조회해서 엔티티 객체를 생성하면, 프록시 객체는 실제 엔티티 객체의 참조를 멤버변수에 보관하고 실제 엔티티 객체의 getName() 을 호출해서 반환합니다. 

 

이것이 프록시를 사용하여 데이터를 가져오는 방법입니다. 

 

프록시는 여러가지 특징을 갖게 되는데요, 이것도 한번 알아보겠습니다. 

 

  • 프록시 객체는 처음 사용할 때 한번만 초기화됩니다. 
  • 프록시 객체를 초기화 한다고 프록시 객체가 실제 엔티티로 바뀌는 것이 아니고, 프록시 객체를 통해서 실제 엔티티에 접근할 수 있습니다. 
  • 프록시 객체는 엔티티를 상속받은 객체이므로 타입 체크 시 주의해야합니다.
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 프록시가 아닌 실제 엔티티를 반환합니다.
  • 프록시의 초기화는 영속성 컨텍스트가 관여하므로 준영속 상태의 프록시를 초기화 하면 문제가 생깁니다.

어떻게 생각하면 당연한 것도 같지만, 그래도 한번 짚고 넘어가야한다고 생각합니다. 

 

다음은 식별자와 관련된 내용입니다. 엔티티를 프록시로 조회할 때, 식별자 값을 파라미터로 전달하는데 프록시 객체는 이 값을 보관합니다. 보관한다는 것은 이 식별자만 조회하고자 할때, 프록시 객체 초기화가 필요없다는 뜻이죠.

 

이는 단순한 연관관계 매핑에서 유용하게 사용할 수 있습니다. 식별자 값을 가지고 있기때문에 쿼리한번을 안날려도 되는 것이죠.

 

이렇게 프록시의 기초에 대해서 알아보았는데요, 다음에는 지연 로딩즉시 로딩에 대해서 다뤄보겠습니다. 👍