본문 바로가기
JAVA/JPA

[JPA] JPA 프록시 정리

by 개미가되고싶은사람 2024. 11. 14.

목차

    해당 글을 읽기전에 프록시 객체에 대해서 알고 오시는 걸 추천합니다.

     

     

    JPA에서는 왜 프록시 객체를 사용할까??

    JPA에서는 프록시 객체를 사용하여 객체지향의 장점을 살리면서 관계형 데이터베이스의 성능 문제를 해결합니다. 객체는 연관된 객체들을 자유롭게 탐색하고 조회할 수 있지만, 관계형 데이터베이스에 매핑된 엔티티 객체는 연관된 테이블의 데이터를 조회하기 위해 JOIN을 사용해야 하므로 자유도가 떨어집니다.

    여기서 '자유도가 떨어진다'는 것은, 객체지향에서는 객체 간의 관계를 통해 쉽게 데이터를 탐색할 수 있지만, 관계형 데이터베이스에서는 연관된 테이블의 데이터를 조회하기 위해 JOIN 쿼리를 작성해야 하기 때문에 자유도가 떨어진다는 의미입니다.

     

    추가적으로, 실제로 연관된 테이블을 사용하지 않는 경우에도 불필요한 JOIN으로 인해 성능이 악화될 수 있습니다. 이러한 문제를 해결하기 위해 JPA에서는 프록시 객체를 사용했습니다. 프록시 객체는 연관된 객체를 처음부터 데이터베이스에서 조회하는 것이 아니라, 실제 사용하는 시점에 데이터베이스에서 조회할 수 있도록 해줍니다. 즉, 프록시 객체는 메모리 사용을 줄이고 초기 로딩 시 불필요한 데이터 로드를 방지하여 성능을 향상시킵니다.

     

     

    JPA 프록시란

    프록시 객체는 실제 클래스를 상속받아 만들어지며, 실제 클래스와 겉 모양이 같아서 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있습니다. 프록시 객체는 실제 객체의 참조(target)를 보관하고 있어, 필요할 때 실제 객체에 접근할 수 있도록 해줍니다.

     

     

    JPA 프록시 객체의 동작 원리

    1. 프록시 객체 생성

    JPA는 엔티티를 직접 로드하는 대신, 해당 엔티티의 프록시 객체를 생성합니다. 이 프록시는 실제 엔티티에 대한 참조(target =기본키)를 가지고 있습니다.

     

    2.초기화 요청

    클라이언트가 프록시 객체의 메서드(예: getName())를 호출하면, 프록시 객체는 영속성 컨텍스트에 초기화 요청을 보냅니다.


    3. DB 조회

    초기화 요청을 받은 영속성 컨텍스트는 데이터베이스에서 해당 엔티티의 정보를 조회하기 위해  쿼리를 실행합니다.


    4. 실제 Entity 생성

    데이터베이스에서 조회한 정보를 바탕으로 영속성 컨텍스트는 실제 엔티티 객체를 생성합니다. 이 과정에서 프록시 객체는 이제 실제 엔티티의 데이터를 메모리에 로드합니다.


    5. 초기화 후의 동작

    한 번 초기화된 실제 엔티티는 이후에는 프록시 객체를 통해 직접 접근할 수 있으며, 추가적인 초기화 요청이 필요하지 않습니다.

     

     

    프록시의 특징 

    • 프록시 객체는 처음 사용할 때 한 번만 초기화됩니다.
    • 프록시 객체를 초기화할 때, 프록시 객체는 실제 엔티티로 변하지 않습니다. 초기화가 완료되면, 프록시 객체를 통해 실제 엔티티에 접근할 수 있습니다.
    • 프록시 객체는 원본 엔티티를 상속받기 때문에, 타입 체크 시 주의가 필요합니다. == 비교는 상황에 따라 실패할 수 있어서 instanceof를 사용해야 합니다.
    • 영속성 컨텍스트에 원하는 엔티티가 이미 존재하면 em.getReference()를 호출해도 실제 엔티티 반환합니다.
    • 준영속 상태일 때, 프록시를 초기화하면 예외 발생 
      • (하이버네이트는 org.hibernate.LazyInitializationException 예외)

    프록시와 원본 객체가 다른 경우

    Member member1 = new Member();
    member1.setName("test");
    em.persist(member1);
    
    Member member2 = new Member();
    member2.setName("test2");
    em.persist(member2);
    
    em.flush();
    em.clear();
    
    Member member = em.find(Member.class, member1.getId());
    Member proxyMember = em.getReference(Member.class, member2.getId());
    System.out.println("m1 == m2 : " + (member.getClass() == proxyMember.getClass()));

     

    준영속 상태와 초기화 요청한 경우

    ===================준영속 상태================
    em.detach(proxyMember);
    em.clear();
    em.close();
    ===================준영속 상태================
    proxyMember.getName(); // 다시 초기화 요청