[JPA] 즉시로딩(Eager)과 지연로딩(Lazy)

웹 프로젝트를 진행하던 중에 양방향 매핑 관계에서 발생한 N+1 문제를 겪었다.
N+1 문제의 이유와 해결 방법에 앞서 즉시로딩과 지연로딩에 대해 먼저 이해해야 한다고 생각한다.

즉시로딩과 지연로딩

즉시로딩과 지연로딩은 양방향 매핑이 되어 있는 두 Entity가 있고 한 Entity를 조회할 때 매핑된 Entity를 같이 조회할지, 실제로 매핑된 Entity의 조회가 필요한 시점에 조회할지를 정하는 방법이다.
비즈니스 로직에서 단순히 특정 Entity와 관련된 로직만 사용하는데 매핑된 Entity까지 조회하면 비효율적일 것이다. JPA는 이 문제를 지연로딩을 사용해서 프록시로 조회하는 방법으로 해결한다.

다음 예시를 통해 자세히 알아보자.

지연로딩(Lazy)

@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    private String password;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Post> postList;
}
@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private String writer;

    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
}
  • User, Post Entity가 있고 Post Entity를 조회하는 로직이 있다면, Post Entity가 로딩되는 시점에 Lazy 로딩 설정이 되어 있는 User Entity는 프륵시 객체로 가져온다.
  • 후에 실제 객체를 사용하는 시점에 쿼리가 실행된다.
    • getUser() 함수로 User를 조회하면 프록시 객체가 조회된다.
    • getUser().getXXX() 함수로 User의 필드에 접근할 때, 쿼리가 실행된다.
  • 대부분 로직에서 두 Entity를 같이 사용한다면 Lazy을 사용할 경우 select 쿼리가 2번씩 실행된다.
    이는 속도가 매우 느린 디스크에 2번 엑세스해야 한다는 것이기 때문에 매우 비효율적이다.
    이때는 즉시로딩(Eager)를 사용해 두 Entity를 함께 조회하는 것이 효율적이다.

즉시로딩(Eager)

@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    private String password;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
    private List<Post> postList;
}
@Entity
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private String writer;

    @ManyToOne(fetch = FetchType.EAGER)
    private User user;
}
  • 즉시로딩은 지연로딩과 달리 Entity 조회 할 때 프록시 객체가 아닌 조인을 사용해서 매핑된 Entity를 함께 조회한다.
  • 가급적이면 즉시로딩을 사용하지 않는 것이 좋다.
    • 즉시로딩을 사용하면 JPQL에서 N+1 문제가 발생한다. (지연로딩을 사용한다고 N+1 문제가 발생하지 않는 것은 아니다.. 다른 방법으로 해결해야 됨)
      N+1 문제에 대해서는 다음에 포스팅할 것이다.

좋은 웹페이지 즐겨찾기