본문 바로가기

Tech/JPA

[JPA] 프록시와 지연로딩

반응형

1. 프록시

프록시 사용 이유

다른 엔티티와 연관관계를 가지고 있는 엔티티의 객체를 조회하는 경우 연관관계가 있는 객체들도 조회하게 된다. 이 경우에 불필요한 객체들이 함께 조회되면서 리소스 낭비가 발생하게 될 수 있다. JPA 에서는 이러한 문제를 프록시와 지연로딩을 사용하여 해결한다.

프록시 객체 조회

엔티티를 조회하는 방식으로는 다음의 두가지가 있다.

em.find(): 바로 DB 에서 조회하여 실제 엔티티 객체 조회.
em.getReference(): DB 조회를 미루고 가짜 (프록시) 엔티티 객체 조회. 해당 객체의 필드를 조회하는 경우에 쿼리를 수행하여 DB 조회를 수행한다.

프록시 엔티티는 실제 클래스를 상속받아서 생성된다. 실제 클래스와 형태가 같아서 사용자 입장에서 실제 객체인지 프록시 객체인지 구분없이 사용할 수 있다. getClass() 를 통해서 조회된 객체의 클래스 정보를 출력하는 경우에는 프록시 클래스임을 알 수 있다.

 

프록시 객체는 실제 객체의 참조 target 을 저장하고 있는데, 프록시 객체가 호출되면 프록시 객체는 실제 객체를 참조하고 있는 target 을 호출한다.

프록시 객체 초기화

프록시 객체는 객체의 메서드가 호출되는 시점에 DB 에서 실제 객체를 조회하여 참조 target 을 초기화한다. 초기화의 순서는 다음과 같다.

1) 프록시 객체의 메서드 호출
2) 영속성 컨텍스트로 초기화 요청 - 프록시 객체가 값을 가지고 있지 않은 경우 영속성 컨텍스트에게 실제 객체의 값을 요청한다.
3) DB 조회 - 영속성 컨텍스트에 해당 객체가 없는 경우 DB 를 조회한다.
4) 실제 Entity 생성 - DB 에서 조회된 값을 통해 실제 엔티티 객체를 생성한다.
5) 프록시 객체의 target 참조 - 프록시 객체의 target 멤버가 생성된 실제 객체를 참조하여 값에 접근한다.

프록시 특징

- 프록시 객체는 초기에 한 번만 초기화된다.

- 프록시 객체가 초기화 된다고 해서 실제 엔티티 객체로 변경되는 것이 아니라, 프록시 객체의 target 이 실제 엔티티 객체를 참조하여 접근할 수 있도록 한다.

- 프록시 객체는 엔티티 객체를 상속받는다. 따라서 타입 체크시에 주의해야 한다. '==' 비교는 실패한다. 대신 instanceof 를 사용하여 타입을 비교한다. em.find(...) == em.getReference(...) 는 무조건 false 를 출력한다.

- 영속성 컨텍스트에 엔티티 객체가 있는 경우, getReference() 를 호출해도 프록시가 아닌 실제 엔티티 객체가 반환된다.

-  영속성 컨텍스트의 기능을 사용할 수 없는 준영속 상태의 경우, 프록시를 초기화할 수 없다. (EntityManager 가 close 되거나, 해당 객체가 detach() 되는 경우)

2. 즉시로딩과 지연로딩

프록시의 사용 이유에서 기술한 것처럼 연관관계에 있는 객체들을 조회하는 경우에 불필요한 객체까지 함께 조회될 수 있다. JPA 에서는 지연로딩을 통해 프록시를 조회하도록 하여 이 문제를 해결한다.

지연로딩

JPA 의 연관관계 어노테이션에서 fetch 옵션으로 LAZY 를 주어서 지연로딩을 설정한다. 지연로딩으로 설정된 객체는 프록시 객체로 조회 되었다가 해당 객체의 메서드 호출 등 직접 접근하는 경우에 실제 엔티티 객체를 가져온다.

 

class Member {
    // ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

 

위와 같이 Member 엔티티에서 Team 객체를 연관관계에 LAZY 옵션으로 지정하는 경우, Member 타입의 객체를 조회할 때 해당 객체가 참조하고 있는 team 변수는 실제 Team 엔티티 객체가 아닌 프록시 객체를 참조하게 된다. 만약 이후에 해당 객체의 team 객체에 접근하게되면 그때 실제 엔티티 객체를 조회한다.

즉시로딩

연관관계에 있는 두 객체를 같이 사용하는 경우가 많은 경우에는 지연로딩이 아닌 즉시로딩을 지정하여 사용할 수 있다.

 

class Member {
    // ...
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "team_id")
    private Team team;
}

 

위의 예제와 같이 Fetch 옵션을 EAGER 로 주어서 지정할 수 있으며, Member 객체를 조회하는 경우 Team 객체도 함께 조회된다. JPA 가능한 경우 조인을 사용하여 SQL 한번에 함께 조회한다.

프록시 사용시의 주의사항

실무에서는 가급적으로 지연로딩을 사용하도록 한다.

즉시로딩의 경우 객체 조회시에 객체들의 연관관계로 인해서 의도하지 않은 매우 긴 조인문의 쿼리가 발생할 수 있다. 그리고 JPQL 에서 N 개의 객체를 조회 시에 연관관계에 있는 객체들을 조회하기 위해 N 개 만큼 쿼리가 추가로 작성되는 N + 1 문제와 같은 경우가 발생하기도 한다.

이러한 문제들을 방지하기 위해서 지연 로딩을 권장하는데, @ManyToOne, @OneToOne 의 경우에는 기본 설정이 즉시로딩이기 때문에 따로 코드레벨에서 LAZY 로 설정해주어야 한다.

반응형

'Tech > JPA' 카테고리의 다른 글

[JPA] 값 타입  (0) 2022.06.04
[JPA] 영속성 전이 (CASCADE)  (0) 2022.05.26
[JPA] 상속관계 매핑  (0) 2022.05.23
[JPA] 연관관계 매핑 종류  (0) 2022.05.17
[JPA] 연관관계 매핑 (2) - 양방향 연관관계  (0) 2022.05.11