본문 바로가기
JPA/JPA 기초

[JPA] 프록시 객체와 지연로딩 정리

by 옹알이옹 2023. 8. 27.
목차

1. 프록시 객체란

2. 프록시 객체 사용 이유

2-1. 프록시 객체를 이용한 지연 로딩

2-2. 지연 로딩 즉시 로딩 차이


 1. 프록시 객체란

프록시 객체는 실제 Entity를 대신하는 가짜 객체이다. 사용법을 보면은 무슨 뜻인지 이해가 갈 것이다.

먼저 java에서 프록시 객체를 생성하는 방법은 이렇다.

entityManager.getReference(타겟엔티티.class, 엔티티의PK)

getReference함수의 리턴값을 확인해 보면 'the found entity instance'라고 적혀있다. 즉 엔티티 객체를 돌려준단 것이다.

그러면 getReference 함수를 통해서도 Entity 객체가 조회되고 find() 함수를 통해서도 Entity가 조회되는데 
무슨 차이가 있는걸까?

그 차이는 getClass()를 직접 로깅을 해보는 순간 다르단 걸 알 수 있다.

EntityManager em = emf.createEntityManager();

EntityTransaction tx = em.getTransaction();
tx.begin();

try{

    Book book1 = em.getReference(Book.class, 1L);
    Book book2 = em.find(Book.class, 2L);

    System.out.println("book1 : "+book1.getClass());
    System.out.println("book2 : "+book2.getClass());

    tx.commit();
}catch (Exception e){
    e.printStackTrace();
    tx.rollback();
}finally {
    em.close();
}
emf.close();

 

실행 결과

프록시 객체 로깅 화면

getReference를 사용하여 조회 한 book1 객체는 엔티티 뒤에 $HibernateProxy$WvxlftUz 가 붙어 프록시 객체란 걸 알 수 있다.

그렇다면 외부적으로는 엔티티와 동일하지만 내부는 엄연히 다르단 것을 확인했다 그러면 왜 굳이 저런 객체를 사용을 할까

 


 2. 프록시 객체 사용 이유

JPA에서 프록시 객체를 사용하는 이유는 객체를 사용하여 로깅같은 어떠한 행위를 할 때 실제 DB를 조회 한다는 것이다.

아래는 실제 예시이고, Author 객체와 Book Entity가 존재하고 Book 객체 안에서 Author 객체를 참조하고 있다.

EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{

            System.out.println("================================================");
            Book book1 = em.getReference(Book.class, 1L);
            System.out.println("================================================");

            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
            Book book2 = em.find(Book.class, 2L);
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");

            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();

실행 결과

지연 로딩 활용 결과 화면

상황은 아래와 같다.

  • 먼저와 다르게 getRefence, find를 사용하여 조회만 하였고, 로깅을 하지 않았다.
  • em.find를 사용했을 때는 바로 DB를 조회하는 쿼리가 날아간다.
  • em.getRerence를 사용한 부분에서는 DB 조회 쿼리가 날아가지 않는다.

그렇다면 조회를 한 뒤 sysout을 통해 화면에 출력을 하면 어떻게 될까.

EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{

            System.out.println("================================================");
            Book book1 = em.getReference(Book.class, 1L);
            System.out.println("book1 :"+book1);
            System.out.println("================================================");

            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
            Book book2 = em.find(Book.class, 2L);
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");

            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();

실행 결과

프록시 객체 쿼리 호출

위와 같이 DB 조회 쿼리가 날아가게 된다. 

 

이러한 프록시 객체를 이용한 것이 JPA에서의 지연로딩이다.

 

2-1. 프록시 객체를 이용한 지연 로딩

JPA에서 조인을 할 때 지연로딩을 사용하게 되면 연관 객체가 프록시 객체로 조회가 된다. 예시는 아래와 같다.

 

Book 엔티티

public class Book {
    @Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
    private Long id;

    @Column(name = "book_name")
    private String bookName;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    public void setId(Long id) {
        this.id = id;
    }

    public void setBookName(String userName) {
        this.bookName = bookName;
    }
}

 

Author 엔티티

public class Author {
    @Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
    private Long id;
    private String name;
}

DB 데이터

  • Book 엔티티가 Author 엔티티와 자식-부모 관계를 가지며 실제 DB에는 위와 같은 데이터가 존재한다.
  • Book엔티티의 Author 필드에 @ManyToOne(fetch = FetchType.LAZY)를 붙여 지연 로딩을 사용하게 한다.
EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{

            System.out.println("================================================");
            Book book = em.find(Book.class, 1L);
            System.out.println("author : "+book.getAuthor().getClass());
            System.out.println("================================================");

            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();

 

실행 결과

연관 객체 프록시 객체 확인

  • Book 엔티티를 조회하여도 연관 객체인 Author에 대한 쿼리는 실제 발생하지 않는다.
  • 조회한 book 객체의 Author 객체 class를 로깅하면 프록시 객체로 조회 된것이 확인된다.

위에서 보았듯이 지연로딩은 프록시 객체를 이용하는 것을 확인할 수 있다.

 

2-2. 지연 로딩 즉시 로딩 차이

JPA에서 즉시 로딩은 Book 엔티티를 조회하면 연관 객체인 Author 객체도 동시에 조회하여 가져오는 것을 말한다.

 

즉시로딩으로 바꾸어 테스트

public class Book {
    @Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
    private Long id;

    @Column(name = "book_name")
    private String bookName;
    @ManyToOne/*(fetch = FetchType.LAZY)*/
    @JoinColumn(name = "author_id")
    private Author author;

    public void setId(Long id) {
        this.id = id;
    }

    public void setBookName(String userName) {
        this.bookName = bookName;
    }
}
  • 먼저 지연 로딩 테스트를 위한 사용한 (fetch=FetchType.LAZY)를 주석한다. 
  • 기본적으로 자식 객체를 조회할 때 부모 객체를 즉시로딩 한다. (@ManyToOne(fetch = FetchType.EAGER) 기본값)

Book 객체 조회 실행 시

EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try{

        System.out.println("================================================");
        Book book = em.find(Book.class, 1L);
        System.out.println("================================================");
        
        tx.commit();
    }catch (Exception e){
        e.printStackTrace();
        tx.rollback();
    }finally {
        em.close();
    }
    emf.close();
}

 

실행 결과

즉시로딩 결과 화면

지연 로딩과 다르게 즉시 로딩은 Book 객체만 조회해도 부모 관계인 Author 객체도 조인하여 가져온다.

 

위와 같은 이유로 연관 관계를 설정할 때 즉시 로딩을 사용하게 되면 원하지 않는 상황에서도 항상 연관 객체를 같이 조회하게 되고 그에 따라 당연히 필요 없는 자원을 낭비하고 성능을 떨어트릴 수 있다.

이러한 이유로 특수한 경우를 제외하면 즉시 로딩 사용을 피하는 것이 좋다.

 

참고 자료

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

반응형