즉시 로딩과 지연 로딩은 무엇일까?
JPA의 프록시
--
JPA의 프록시와 일반적인 프록시는 같은 개념이지만 각자 목적이 다르다.
JPA에서 사용하는 프록시도 그냥 "프록시"라고 부르지만
정확하게 명칭 할 때에는
"JPA 프록시" 또는 "하이버네이트 프록시", "지연 로딩 프록시"라고도 부른다.
일반적으로 프록시는
"대리인" 역할을 하는 객체로
실제 객체 대신 가짜 객체(프록시)를 만들어서 대신 동작하게 하는 패턴이다.
- 원본 객체의 접근을 제어
- 원본 객체가 무겁거나 생성 비용이 크면 프록시가 대신 처리
- 원본 객체를 감싸서 추가 기능을 제공 (ex. 로깅, 캐싱, 접근 제어 등)
예시로
네트워크 프록시 인터넷에서
VPN이나 프록시 서버를 사용하면,
내 요청을 대신 전달해 주는 서버가 프록시 역할을 수행한다.
JPA의 프록시는
일반적인 프록시 개념을 적용하지만,
이를 통해 주로 엔티티의 데이터 로딩을 지연시키기 위한 프록시로 사용된다.
기존에 DB를 통해 엔티티의 데이터를 조회할 때
em.find() 메서드를 통해 DB에 조회하는 쿼리를 날려 데이터를 조회한다.
JPA의 프록시를 사용하기 위해서는 em.find() 메서드가 아닌 em.getReference() 메서드를 사용한다.
em.getReference() 메서드는
DB 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 것으로
해당 메서드를 실행하는 시점에는 아무 동작도 하지 않는다. (쿼리를 날리지 않음)
즉, 가짜로 가져온 척을 한다.
이후에 진짜 값이 필요할 때 그제야 쿼리를 진짜로 날린다.
정리하면
find()는
진짜 엔티티 객체를 바로 조회하여 데이터를 제공하지만
getReference()는
하이버네이트가 자기 내부에 특정 라이브러리를 사용하여 가짜(프록시) 엔티티 객체를 조회하여 제공한다.

이때 조회한 프록시 엔티티 구조는 기존 진짜 엔티티와 겉은 동일하지만 내용은 비어있는 상태가 된다.
그리고 내부에 target 속성이 존재하게 되는데 해당 값은 진짜 엔티티를 가리키는 속성으로
초기에는 빈 껍데기므로 null값을 가지고 진짜 조회가 이루어지면 조회한 엔티티를 가리키게 된다.
프록시 특징

- 실제 클래스(엔티티)를 상속 받아서 생성된다.
(하이버네이트가 내부적으로 프록시 라이브러리를 이용하여 만들어지므로 실제 엔티티와 겉모양은 동일) - 이론상 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 target을 통해 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화 과정 (target 설정 과정 = target 값이 null)

- getReferenc() 메서드를 통해 프록시 객체 생성
- getName()을 통해 값을 조회. 하지만 아직 target = null 로 원본 엔티티 접근이 불가능하여 조회 불가능
- JPA는 영속성 컨텍스트로 원본 객체를 가져오라고 요청 (초기화 요청)
- 영속성 컨텍스트는 해당 DB를 조회
- 조회한 DB의 실제 엔티티를 생성
- 생성된 엔티티와 프록시의 target을 연결
이후부터는
getName()을 하면 target을 통해 진짜 객체의 getName()을 통해 반환 가능
정리
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 하는 것은 프록시 객체가 실제 객체 엔티티로 변경되는 것이 아닌
프록시 객체를 통해 실제 엔티티에 접근이 가능해지는 것 - 프록시 객체는 원본 엔티티를 상속받음
(그래서 타입 체크 시 주의 / 즉, (==) 비교 대신 (instance of) 비교를 사용) - 영속성 컨텍스트에 찾는 엔티티가 이미 존재하면 getReference()를 호출해도 실제 엔티티 반환
- 영속성 컨텍스트에서 관리하지 않는 상태(준영속) 일 때 프록시 초기화 시 문제 발생
(하이버네이트는 org.gibernate.LazyInitializationException 예외 발생)
프록시 관련 명령어
- PersistenceUnitUtil.isLoaded(Object entity) : 해당 프록시의 초기화 여부 확인
- Hibernate.initialize(entity) : 프록시 강제 초기화
--
즉시 로딩 (Eager Loading), 지연 로딩 (Lazy Loading)
--
즉시 로딩과 지연로딩은
JPA에서 엔티티를 조회할 때,
연관된 엔티티를 언제 로딩할지를 결정하는 방법이다.
- 즉시 로딩 : 연관된 엔티티를 한 번에 같이 가져오는 방식
- 지연 로딩 : 연관된 엔티티를 실제로 사용할 때까지 쿼리를 미루고 실제 사용할 때 마저 가져오는 방식

위 그림과 같은 연관관계가 있다고 가정하자.
일반적으로 Member를 조회할 때 Member는 Team을 참조하고 있기 때문에 Team도 같이 조회된다.
(= 즉시 로딩)
하지만 Member만 조회하고 싶은 상황에서 Team까지 같이 조회되면 비효율적이게 되므로
이를 해결하기 위해 JPA는 지연 로딩을 지원한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "username")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
...
}
즉시 로딩과 지연 로딩은
연관관계를 매핑하는 어노테이션(ex. @ManyToOne)에 "fetch" 옵션으로 설정할 수 있다.
- FetchType.LAZY : 지연 로딩
- FetchType.EAGER : 즉시 로딩
연관관계 매핑 어노테이션의 fetch 기본값
- @OneToOne : FetchType.EAGER
- @ManyToOne : FetchType.EAGER
- @OneToMany : FetchType.LAZY
- @ManyToMany : FetchType.LAZY
지연 로딩으로 설정을 한다면
Member를 조회할 때 Team은 프록시 객체로 조회하여
실제로는 Member 엔티티만 조회하는 것이 된다. (Team은 프록시 객체로 조회하여 Member 조회 쿼리만 전달된다.)
이후 Member만 조회하고 사용하다가
Team의 내용이 필요하게 되면 그때 Team을 조회하는 쿼리를 전달하여 사용할 수 있다.
(이때 프록시 객체(Team)가 초기화된다.)
정리
- 매핑된 엔티티를 자주 같이 사용하면 "즉시 로딩"을, 아니면 "지연 로딩"을 사용
- 실무에서는 지연 로딩만 사용하는 것을 권장
(즉시 로딩은 예상하지 못한 SQL문이 발생 / 만약 여러 객체가 연결되어 있다면 전부 조회됨) - 즉시 로딩은 JPQL에서 N + 1 문제를 일으킴
--
참고 및 출처
'JPA' 카테고리의 다른 글
JPA의 데이터 타입 (0) | 2025.04.04 |
---|---|
영속성 전이 (CASCADE) (+ 고아 객체) (0) | 2025.04.03 |
상속관계 매핑 (+@MappedSuperclass) (1) | 2025.04.01 |
연관관계 매핑 (단방향, 양방향, 1:1, 1:N, N:1, N:N) (0) | 2025.03.31 |
엔티티(Entity) 매핑 (= 객체와 DB 테이블 연결) (0) | 2025.03.30 |
즉시 로딩과 지연 로딩은 무엇일까?
JPA의 프록시
--
JPA의 프록시와 일반적인 프록시는 같은 개념이지만 각자 목적이 다르다.
JPA에서 사용하는 프록시도 그냥 "프록시"라고 부르지만
정확하게 명칭 할 때에는
"JPA 프록시" 또는 "하이버네이트 프록시", "지연 로딩 프록시"라고도 부른다.
일반적으로 프록시는
"대리인" 역할을 하는 객체로
실제 객체 대신 가짜 객체(프록시)를 만들어서 대신 동작하게 하는 패턴이다.
- 원본 객체의 접근을 제어
- 원본 객체가 무겁거나 생성 비용이 크면 프록시가 대신 처리
- 원본 객체를 감싸서 추가 기능을 제공 (ex. 로깅, 캐싱, 접근 제어 등)
예시로
네트워크 프록시 인터넷에서
VPN이나 프록시 서버를 사용하면,
내 요청을 대신 전달해 주는 서버가 프록시 역할을 수행한다.
JPA의 프록시는
일반적인 프록시 개념을 적용하지만,
이를 통해 주로 엔티티의 데이터 로딩을 지연시키기 위한 프록시로 사용된다.
기존에 DB를 통해 엔티티의 데이터를 조회할 때
em.find() 메서드를 통해 DB에 조회하는 쿼리를 날려 데이터를 조회한다.
JPA의 프록시를 사용하기 위해서는 em.find() 메서드가 아닌 em.getReference() 메서드를 사용한다.
em.getReference() 메서드는
DB 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 것으로
해당 메서드를 실행하는 시점에는 아무 동작도 하지 않는다. (쿼리를 날리지 않음)
즉, 가짜로 가져온 척을 한다.
이후에 진짜 값이 필요할 때 그제야 쿼리를 진짜로 날린다.
정리하면
find()는
진짜 엔티티 객체를 바로 조회하여 데이터를 제공하지만
getReference()는
하이버네이트가 자기 내부에 특정 라이브러리를 사용하여 가짜(프록시) 엔티티 객체를 조회하여 제공한다.

이때 조회한 프록시 엔티티 구조는 기존 진짜 엔티티와 겉은 동일하지만 내용은 비어있는 상태가 된다.
그리고 내부에 target 속성이 존재하게 되는데 해당 값은 진짜 엔티티를 가리키는 속성으로
초기에는 빈 껍데기므로 null값을 가지고 진짜 조회가 이루어지면 조회한 엔티티를 가리키게 된다.
프록시 특징

- 실제 클래스(엔티티)를 상속 받아서 생성된다.
(하이버네이트가 내부적으로 프록시 라이브러리를 이용하여 만들어지므로 실제 엔티티와 겉모양은 동일) - 이론상 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 target을 통해 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화 과정 (target 설정 과정 = target 값이 null)

- getReferenc() 메서드를 통해 프록시 객체 생성
- getName()을 통해 값을 조회. 하지만 아직 target = null 로 원본 엔티티 접근이 불가능하여 조회 불가능
- JPA는 영속성 컨텍스트로 원본 객체를 가져오라고 요청 (초기화 요청)
- 영속성 컨텍스트는 해당 DB를 조회
- 조회한 DB의 실제 엔티티를 생성
- 생성된 엔티티와 프록시의 target을 연결
이후부터는
getName()을 하면 target을 통해 진짜 객체의 getName()을 통해 반환 가능
정리
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 하는 것은 프록시 객체가 실제 객체 엔티티로 변경되는 것이 아닌
프록시 객체를 통해 실제 엔티티에 접근이 가능해지는 것 - 프록시 객체는 원본 엔티티를 상속받음
(그래서 타입 체크 시 주의 / 즉, (==) 비교 대신 (instance of) 비교를 사용) - 영속성 컨텍스트에 찾는 엔티티가 이미 존재하면 getReference()를 호출해도 실제 엔티티 반환
- 영속성 컨텍스트에서 관리하지 않는 상태(준영속) 일 때 프록시 초기화 시 문제 발생
(하이버네이트는 org.gibernate.LazyInitializationException 예외 발생)
프록시 관련 명령어
- PersistenceUnitUtil.isLoaded(Object entity) : 해당 프록시의 초기화 여부 확인
- Hibernate.initialize(entity) : 프록시 강제 초기화
--
즉시 로딩 (Eager Loading), 지연 로딩 (Lazy Loading)
--
즉시 로딩과 지연로딩은
JPA에서 엔티티를 조회할 때,
연관된 엔티티를 언제 로딩할지를 결정하는 방법이다.
- 즉시 로딩 : 연관된 엔티티를 한 번에 같이 가져오는 방식
- 지연 로딩 : 연관된 엔티티를 실제로 사용할 때까지 쿼리를 미루고 실제 사용할 때 마저 가져오는 방식

위 그림과 같은 연관관계가 있다고 가정하자.
일반적으로 Member를 조회할 때 Member는 Team을 참조하고 있기 때문에 Team도 같이 조회된다.
(= 즉시 로딩)
하지만 Member만 조회하고 싶은 상황에서 Team까지 같이 조회되면 비효율적이게 되므로
이를 해결하기 위해 JPA는 지연 로딩을 지원한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "username")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
...
}
즉시 로딩과 지연 로딩은
연관관계를 매핑하는 어노테이션(ex. @ManyToOne)에 "fetch" 옵션으로 설정할 수 있다.
- FetchType.LAZY : 지연 로딩
- FetchType.EAGER : 즉시 로딩
연관관계 매핑 어노테이션의 fetch 기본값
- @OneToOne : FetchType.EAGER
- @ManyToOne : FetchType.EAGER
- @OneToMany : FetchType.LAZY
- @ManyToMany : FetchType.LAZY
지연 로딩으로 설정을 한다면
Member를 조회할 때 Team은 프록시 객체로 조회하여
실제로는 Member 엔티티만 조회하는 것이 된다. (Team은 프록시 객체로 조회하여 Member 조회 쿼리만 전달된다.)
이후 Member만 조회하고 사용하다가
Team의 내용이 필요하게 되면 그때 Team을 조회하는 쿼리를 전달하여 사용할 수 있다.
(이때 프록시 객체(Team)가 초기화된다.)
정리
- 매핑된 엔티티를 자주 같이 사용하면 "즉시 로딩"을, 아니면 "지연 로딩"을 사용
- 실무에서는 지연 로딩만 사용하는 것을 권장
(즉시 로딩은 예상하지 못한 SQL문이 발생 / 만약 여러 객체가 연결되어 있다면 전부 조회됨) - 즉시 로딩은 JPQL에서 N + 1 문제를 일으킴
--
참고 및 출처
'JPA' 카테고리의 다른 글
JPA의 데이터 타입 (0) | 2025.04.04 |
---|---|
영속성 전이 (CASCADE) (+ 고아 객체) (0) | 2025.04.03 |
상속관계 매핑 (+@MappedSuperclass) (1) | 2025.04.01 |
연관관계 매핑 (단방향, 양방향, 1:1, 1:N, N:1, N:N) (0) | 2025.03.31 |
엔티티(Entity) 매핑 (= 객체와 DB 테이블 연결) (0) | 2025.03.30 |