241202 TIL - JPA Proxy 정리

J_log·2024년 12월 2일
0
post-thumbnail

프록시란?

프록시는 JPA에서 사용되는 가짜 객체이다. JPA는 데이터베이스에서 데이터를 바로 가져오는 대신, 실제 데이터를 감싸는 대리 객체를 생성한다. 이 대리 객체가 필요할 때 데이터를 대신 불러온다. 쉽게 말하면 데이터를 바로 꺼내기보다는 꺼낼 준비만 하고 기다리는 가짜 객체이다.

프록시가 왜 필요한가?

JPA의 핵심 기능 중 하나는 지연 로딩(Lazy Loading)이다. 지연 로딩은 정말 필요한 순간에만 데이터를 가져오는 방식인데, 이걸 구현하려면 프록시가 필요하다.

@Entity
class Product {
    @Id
    private Long id;
    private String name;

    @OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
    private List<Review> reviews; // 상품 리뷰
}

fetch = FetchType.Lazy로 설정하면 상품을 가져올 때 리뷰는 가져오지 않는다. 대신, 리뷰 리스트는 프록시 객체로 초기화된다. 리뷰를 실제로 조회할 때 JPA가 DB에서 데이터를 가져온다.

프록시의 동작 방식

프록시는 객체의 메모리를 아끼고, 불필요한 쿼리를 막는 데 유용하다.

  • 초기 상태 : reviews는 프록시 객체로 초기화된다. (아직 데이터 없음)
  • 필요한 순간 : reviews.get(0).getContent()처럼 데이터를 요청하면 JPA가 자동으로 DB에서 데이터를 가져온다.

주의 해야될 부분

프록시는 편리하지만, 제대로 이해하지 못하면 문제를 일으킬 수 있다.

  • LazyInitializationException
    영속성 컨텍스트가 닫힌 상태에서 프록시 객체를 접근하면 예외가 발생한다.
Product product = productRepository.findById(1L);
List<Review> reviews = product.getReviews(); // Lazy Loading
em.close(); // 영속성 컨텍스트 종료

reviews.get(0); // 예외 발생! LazyInitializationException
  • N+1문제
    연관 데이터를 반복적으로 호출하면 쿼리가 여러 번 실행된다.
List<Product> products = productRepository.findAll(); // 모든 상품 조회
for (Product product : products) {
    System.out.println(product.getReviews().size()); // 리뷰 조회 (쿼리 발생)
}

상품이 10개라면 1번의 상품 조회 + 10번의 리뷰 조회 쿼리가 발생한다.
(Fetch Join이나 배치 로딩을 이용해 해결 가능)

프록시를 잘 활용하려면?

  1. Lazy와 Eager의 적절한 조합
    필요한 데이터를 미리 로드(Eager)할지, 필요할 때 로드(Lazy)할지 신중히 결정한다.

  2. JPQL 페치 조인 사용
    지연 로딩이 예상되는 데이터를 함께 조회한다.

@Query("SELECT p FROM Product p JOIN FETCH p.reviews WHERE p.id = :id")
Product findByIdWithReviews(@Param("id") Long id);

추가적으로 알게 된 내용

  1. DTO 변환
    필요한 데이터만 가져와 DTO로 변환해 사용한다.
  2. Hibernate.initialize() 사용
    필요할 때 프록시를 강제로 초기화한다.
Hibernate.initialize(product.getReviews());

0개의 댓글