JPA 톺아보기 - 프록시

Janek·2023년 2월 2일
0

JPA 톺아보기

목록 보기
6/10
post-thumbnail

해당 포스팅은 인프런에서 제공하는 김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.

지연로딩

엔티티가 조회될 때 연관관계에 있는 엔티티들을 사용하지 않음에도 함께 조회해 두는 것은 효율적이지 않다. 그렇기에 JPA는 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하며, 이를 지연 로딩이라고 한다.

JPA의 구현체인 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법과 바이트코드를 수정하는 두 가지 방법을 제공하며, 복잡한 설정이 필요한 바이트코드 조작에 비해 프록시는 별도의 설정 없이 사용할 수 있다.

프록시

프록시의 특징

프록시는 실제 클래스를 상속 받아 만들어지며, 실제 클래스와 겉 모양이 같다. 그렇기에 사용자의 입장에서 해당 객체가 원본 객체인지, 프록시 객체인지 구분하지 않고 사용할 수 있다.

프록시 객체는 실제 객체에 대한 참조(target)를 보관하며, 프록시 객체의 메서드가 호출되면 참조를 통해 실제 객체의 메서드를 호출한다. 이 때 실제 객체가 생성되어있지 않다면 해당 객체에 대한 생성을 요청하며, 이를 초기화 한다.

JPA의 관점에서 본다면 아래와 같다.

  1. 프록시 객체 호출
  2. 실제 엔티티의 참조를 통한 호출
    • 실제 엔티티가 존재하지 않을 경우 영속성 컨텍스트에 생성 요청(초기화) 후 멤버 변수에 담음
    • 실제 엔티티 호출
  3. 호출에 대한 결과 반환

프록시의 특징을 정리하면 다음과 같다.

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
  • 초기화 된 프록시는 실제 엔티티로 변환되는 것이 아닌 실제 객체에 대한 접근만 가능해진다.
  • 프록시 객체는 원본 엔티티를 상속받았기에 타입 체크 시 주의해야 한다.
  • 영속성 컨텍스트에 이미 실제 엔티티가 있다면 em.getReference() 호출시 프록시가 아닌 실제 엔티티를 반환한다.
  • 초기화는 영속성 컨텍스트를 통해서만 가능하며, 준영속 상태의 프록시를 초기화할 경우 예외가 발생한다.

프록시와 식별자

엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달 받아 프록시 객체 내부에 보관한다. 그렇기에 식별자 값만 호출된다면 프록시를 초기화 하지 않지만, 엔티티 접근 방식을 프로퍼티(@Access(AccessType.PROPERTY))가 아닌 필드(@Access(AccessType.FIELD))로 설정한 경우에는 초기화한다.

프록시를 사용하면 연관관계를 설정할 때 식별자 값만 사용하므로 데이터베이스 접근 횟수를 줄일 수 있다. 이러한 연관관계 설정시에는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않는다.

프록시 확인

JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity) 메서드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다. 초기화되었거나 프록시 인스턴스가 아닐 경우 true를 반환한다. 또한 하이버네이트의 initialize() 메서드를 통해 강제로 프록시를 초기화할 수도 있다.

즉시 로딩과 지연 로딩

JPA는 연관된 엔티티의 조회 시점을 선택할 수 있도록 엔티티를 조회할 때 연관된 엔티티도 함께 조회하는 즉시 로딩지연 로딩 두 가지 방법을 제공한다.

즉시 로딩(EAGER LOADING)

즉시 로딩을 사용하기 위해서는 @ManyToOne(fetch = FetchType.EAGER)와 같이 연관관계 매핑의 fetch 속성을 FetchType.EAGER로 지정하면 된다.

JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다. 외래 키가 null을 허용할 경우를 대비해 JPA는 외부 조인(OUTER JOIN)을 기본적으로 사용한다. 따라서 null을 허용하지 않는 외래 키를 사용해 조회할 때 최적화를 위해 내부 조인(INNER JOIN)을 사용하기 위해서는 @JoinColumn(..., nullable = false)를 통해 명시해주어야 한다.

지연 로딩(LAZY LOADING)

지연 로딩을 사용하기 위해서는 @ManyToOne(fetch = FetchType.LAZY)와 같이 연관관계 매핑의 fetch 속성을 FetchType.LAZY로 지정하면 된다.

지연 로딩을 사용할 경우 사용하지 않는 연관관계 객체에 프록시 객체를 넣어두고 사용하기 전까지 초기화를 미뤄둔다.

지연 로딩 활용

프록시와 컬렉션 래퍼

하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티 내부에 컬렉션이 있으면 이를 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트 내장 컬렉션을 변경하며, 이를 컬렉션 래퍼라 한다.

엔티티를 지연 로딩할 경우 프록시 객체를 사용하고, 컬렉션을 지연 로딩할 때는 컬렉션 래퍼가 지연 로딩을 처리한다.

JPA 기본 페치 전략

JPA의 기본 페치(fetch) 전략은 연관된 엔티티가 하나일 경우 즉시 로딩을, 컬렉션일 경우 지연 로딩을 사용한다. 그러나 개발 단계에선 모든 연관관계를 지연 로딩으로 설정하고, 조회 빈도가 높은 곳에서만 즉시 로딩을 사용하도록 최적화하는 것이 좋다.

컬렉션을 하나 이상 즉시 로딩하는 것은 권장되지 않는다. 과도한 SQL 실행으로 어플리케이션의 성능을 저하시키는 1+N 문제가 발생할 수 있기 때문이다. 또한 컬렉션 즉시 로딩은 항상 외부 조인을 사용하기 때문에 이를 인지하고 사용해야 한다.

profile
만들고 나누며, 세상을 이롭게 하고 싶습니다.

0개의 댓글