[Spring JPA] fetch전략과 fetch join에 관한 오해

Updated:

I. 개요

[Spring JPA] 프로젝트 중 게시판 쿼리 이슈를 해결하던 도중 굉장히 잘못된 지식을 갖고 있다는 것을 알게 되었다.
내용을 정리하는 편이 기억에 남을 것 같아서 글로 남기려 한다.




II. 오해

1. fetch전략과 fetch join은 같다?

DB에 대한 기본적인 지식 없이 바로 JPA강의를 수강하다보니 깊게 뿌리박힌 생각이었다. 단순히 이름이 같다는 이유만으로 <fetch전략과 fetch join은 같다!> 라고 생각했다.

fetch전략

fetch전략은 특정 엔티티를 조회할 때, 연관관계에 있는 다른 엔티티를 «언제» 불러올까에 대한 옵션이다.

@Entity
public class Post extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "post_id")
    private Long id;

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
    private List<Comment> comments;
}

PostComment가 양방향 관계를 갖고 있다고 생각하자.

  • FetchTypeEAGER일 경우, Post를 조회한 «직후» comments까지 조회하게 된다.
  • FetchTypeLAZY일 경우, Post를 조회하고 «추후» Post를 통해 comments를 사용하려하면 그제서야 comments를 조회하는 쿼리를 날린다.

fetch join

fetch joinJPQL만의 최적화를 위한 join방식이다.
join의 경우 Post만 조회한다면 comments는 조회되지 않아 접근할 수 없지만, fetch joinPostcomments를 «동시»에 조회한다.

fetch전략의 EAGER방식과 fetch join은 같은 것?

그렇지 않다. JPQL을 실행하여 Post를 조회한다고 하자.

  • fetch전략의 EAGER방식: Post를 조회한 «직후» comments를 조회하므로 총 2개의 쿼리를 날리게 된다.
  • fetch join: Postcomments를 «동시»에 조회하므로 총 1개의 쿼리를 날리게 된다.

따라서 둘은 완전히 다른 개념이며, fetch joinfetch 전략을 무시한다.

2. fetch 전략을 LAZY로 설정하면 N+1문제가 일어나지 않는다?

N+1문제란?

@Entity
public class Post extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "post_id")
    private Long id;

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
    private List<Comment> comments;
}

같은 예제에서 Post를 10개 조회한다고 가정하자.
그렇다면 Post를 조회하는 쿼리 1개와 모든 Post에 대해 comments를 조회하는 쿼리 10개를 더해 총 11개의 쿼리를 날리게 된다.
이렇듯 N = 10개의 객체를 조회하기 위해 1 + N(10)개의 쿼리를 날리게 되는 문제를 N+1문제라고 한다.

강의를 들으면서 N+1문제를 방지하려면 LAZY방식을 사용하면 된다고 배웠기에, LAZY방식은 N+1문제와 무관하다고 생각했다.

하지만 실은 N+1문제EAGER방식에 비해 덜 발생하는 것이었다.

LAZY방식일 때

마찬가지로 Post를 10개 조회한다고 가정하자.

만약 Post를 조회했지만, comments에 대한 내용을 호출하지 않는다면 쿼리는 1개만 날리는 것으로 끝날 것이다. 하지만 만약 10개의 Post에 대해 각각의 comments를 호출해야 한다고 하자. 결국 뒤늦게 10개comments를 호출하게 되고, 최종적으로 11개의 쿼리를 날리게 되어 N+1문제가 발생한다.

그럼에도 최선의 경우에는 1개의 쿼리, 최악이어도 EAGER방식과 같은 11개의 쿼리를 날리는 것이 되기 때문에 LAZY를 선호하는 것이라고 생각한다.

Leave a comment