해당 포스팅은 인프런에서 제공하는 김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.
엔티티가 조회될 때 연관관계에 있는 엔티티들을 사용하지 않음에도 함께 조회해 두는 것은 효율적이지 않다. 그렇기에 JPA는 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하며, 이를 지연 로딩이라고 한다.
JPA의 구현체인 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법과 바이트코드를 수정하는 두 가지 방법을 제공하며, 복잡한 설정이 필요한 바이트코드 조작에 비해 프록시는 별도의 설정 없이 사용할 수 있다.
프록시는 실제 클래스를 상속 받아 만들어지며, 실제 클래스와 겉 모양이 같다. 그렇기에 사용자의 입장에서 해당 객체가 원본 객체인지, 프록시 객체인지 구분하지 않고 사용할 수 있다.
프록시 객체는 실제 객체에 대한 참조(target)를 보관하며, 프록시 객체의 메서드가 호출되면 참조를 통해 실제 객체의 메서드를 호출한다. 이 때 실제 객체가 생성되어있지 않다면 해당 객체에 대한 생성을 요청하며, 이를 초기화 한다.
JPA의 관점에서 본다면 아래와 같다.
프록시의 특징을 정리하면 다음과 같다.
em.getReference()
호출시 프록시가 아닌 실제 엔티티를 반환한다.엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달 받아 프록시 객체 내부에 보관한다. 그렇기에 식별자 값만 호출된다면 프록시를 초기화 하지 않지만, 엔티티 접근 방식을 프로퍼티(@Access(AccessType.PROPERTY)
)가 아닌 필드(@Access(AccessType.FIELD)
)로 설정한 경우에는 초기화한다.
프록시를 사용하면 연관관계를 설정할 때 식별자 값만 사용하므로 데이터베이스 접근 횟수를 줄일 수 있다. 이러한 연관관계 설정시에는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않는다.
JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity)
메서드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다. 초기화되었거나 프록시 인스턴스가 아닐 경우 true를 반환한다. 또한 하이버네이트의 initialize()
메서드를 통해 강제로 프록시를 초기화할 수도 있다.
JPA는 연관된 엔티티의 조회 시점을 선택할 수 있도록 엔티티를 조회할 때 연관된 엔티티도 함께 조회하는 즉시 로딩과 지연 로딩 두 가지 방법을 제공한다.
즉시 로딩을 사용하기 위해서는 @ManyToOne(fetch = FetchType.EAGER)
와 같이 연관관계 매핑의 fetch
속성을 FetchType.EAGER
로 지정하면 된다.
JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다. 외래 키가 null을 허용할 경우를 대비해 JPA는 외부 조인(OUTER JOIN)을 기본적으로 사용한다. 따라서 null을 허용하지 않는 외래 키를 사용해 조회할 때 최적화를 위해 내부 조인(INNER JOIN)을 사용하기 위해서는 @JoinColumn(..., nullable = false)
를 통해 명시해주어야 한다.
지연 로딩을 사용하기 위해서는 @ManyToOne(fetch = FetchType.LAZY)
와 같이 연관관계 매핑의 fetch
속성을 FetchType.LAZY
로 지정하면 된다.
지연 로딩을 사용할 경우 사용하지 않는 연관관계 객체에 프록시 객체를 넣어두고 사용하기 전까지 초기화를 미뤄둔다.
하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티 내부에 컬렉션이 있으면 이를 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트 내장 컬렉션을 변경하며, 이를 컬렉션 래퍼라 한다.
엔티티를 지연 로딩할 경우 프록시 객체를 사용하고, 컬렉션을 지연 로딩할 때는 컬렉션 래퍼가 지연 로딩을 처리한다.
JPA의 기본 페치(fetch) 전략은 연관된 엔티티가 하나일 경우 즉시 로딩을, 컬렉션일 경우 지연 로딩을 사용한다. 그러나 개발 단계에선 모든 연관관계를 지연 로딩으로 설정하고, 조회 빈도가 높은 곳에서만 즉시 로딩을 사용하도록 최적화하는 것이 좋다.
컬렉션을 하나 이상 즉시 로딩하는 것은 권장되지 않는다. 과도한 SQL 실행으로 어플리케이션의 성능을 저하시키는 1+N 문제가 발생할 수 있기 때문이다. 또한 컬렉션 즉시 로딩은 항상 외부 조인을 사용하기 때문에 이를 인지하고 사용해야 한다.