본문 바로가기

JPA

[JPA] 프록시, 즉시로딩, 지연로딩

프록시란?

아래와 같은 상황을 가정해보고 프록시가 무엇인지 어떤 상황에서 사용되는지 한번 알아보자

  • 회원 엔티티와 팀 엔티티가 존재하고 두 엔티티는 다대일 단방향 매핑 되어있다.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String username;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

}
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;

}

위와 같은 상황에서 회원 엔티티를 조회하고 객체 그래프 탐색으로 팀 엔티티까지 사용하는 경우도 있지만 로직에 따라 회원 엔티티만 사용하고 팀 엔티티는 전혀 사용하지 않는 경우도 존재한다. (아래와 같은 상황)

public String printUser(Long memberId) {
    Member member = em.find(Member.class, memberId);
    System.out.print(member.getUsername());
}

이런 경우 회원 엔티티를 조회할 때 팀 엔티티까지 조회해 오는 것은 효율적이지 않다. 그래서 JPA는 팀 엔티티를 실제로 사용하는 시점에 팀 엔티티를 조회할 수 있도록 지연 로딩이라는 방법을 제공한다.

 

지연 로딩 기능을 사용하기 위해선 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이 가짜 객체를 프록시 객체라고 한다.

 

프록시 객체를 생성하기 위해서는 엔티티 조회시 EntityManager.find() 메소드 대신 EntityManager.getReference() 메소드를 사용하면 된다.

Member member = em.getReference(Member.class, memberId);

이렇게 프록시 객체를 생성했다가 실제 사용될 때 실제 엔티티 객체가 생성되고 이를 프록시 객체의 초기화라고 한다. 프록시 객체가 초기화 되면 실제 엔티티에 접근할 수 있다.

 

지연로딩을 하기 위해서는 프록시 객체가 필요하다는 것을 알았다. 이제 지연로딩에 대해서 더 알아보고 지연로딩과 함께 JPA에서 제공하는 즉시로딩 방법에 대해서도 알아보자.

즉시로딩과 지연로딩

지연 로딩을 쓰면 엔티티를 실제 사용하는 시점에 조회해올 수 있어 성능상 이점이 있다는 것을 알았다. 그렇다면 항상 지연로딩을 사용하는 것이 좋을까?

만약 연관관계 설정된 두 엔티티가 항상 같이 사용된다면 즉시로딩으로 두 테이블을 조인하여 함께 조회해오는 것이 좋다. 

즉시 로딩 (EAGER LOADING)

즉시 로딩은 @ManyToOne의 fetch 속성을 FetchType.EAGER로 설정해서 사용할 수 있다.

public class Member {
    //...

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

}

즉시 로딩으로 회원 엔티티를 조회하면 JPA가 최적화하기 위해 알아서 아래와 같이 외부조인으로 팀 엔티티를 한번에 조회해 온다. 

지연로딩 (LAZY LOADING)

즉시 로딩은 @ManyToOne의 fetch 속성을 FetchType.LAZY로 설정해서 사용할 수 있다.

public class Member {
    //...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

}
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
team.getName();

지연로딩으로 조회시 회원만 조회하고 팀은 조회하지 않는다. 그리고 team 객체를 생성할때 프록시 객체를 넣어뒀다가 team.getName() 같이 팀 엔티티를 실제로 사용했을때 프록시 객체가 초기화되면서 팀을 조회한다.

만약 영속성 컨텍스트에 이미 팀 엔티티가 로딩되어 있으면 프록시 객체를 사용할 필요없이 바로 실제 객체를 사용한다. 즉 프록시 객체는 처음 사용할 때 한 번만 초기화 된다.

JPA 기본 페치 전략

fetch 속성의 기본 설정값은 다음과 같다.

  • @ManyToOne, @OneToOne : 즉시 로딩(FetchType.EAGER)
  • @OneToMany, @ManyToMany : 지연 로딩(FetchType.LAZY)

JPA의 기본 페치 전략은 연관된 엔티티가 하나면 즉시 로딩을, 컬렉션이면 지연 로딩을 사용한다.

 

지연로딩을 사용할 지 즉시로딩을 사용할 지 고민된다면 일단 모든 연관관계에 지연 로딩을 사용하는 것을 추천한다. 그리고 애플리케이션 개발이 어느 정도 완료단계에 왔을 때 상황에 맞게 꼭 필요한 곳에만 즉시로딩을 사용하도록 최적화 해주면 된다.

'JPA' 카테고리의 다른 글

[JPA] 객체지향 쿼리  (0) 2022.10.10
[JPA] 영속성 전이 (CASCADE), 고아객체 제거 (ORPHAN)  (0) 2022.10.05
[JPA] 연관관계 매핑  (0) 2022.10.02
[JPA] 영속성 컨텍스트란(Persistence Context)?  (0) 2022.10.02
[JPA] JPA 란?  (0) 2022.10.01