https://velog.io/@hyeonjoonpark/Spring-Boot-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91
이 블로그에서 연관관계 매핑에 대해서 설명하다가 N+1문제가 잠깐 언급되었는데 이번 글에서는 N+1 문제에 관해 조금 더 자세히 설명해보고자 한다.
https://www.youtube.com/watch?v=ni92wUkAmQI
유튜브 우아한테크 수달님의 내용을 바탕으로 공부 한 내용을 정리해보았다

N+1문제란

요청이 1개의 쿼리로 처리되길 기대했는데 N개의 추가 쿼리가 발생하는 현상
N+1 은 다양한 상황에서 발생되지만 @OneToMany 일대다 관계에서의 N+1에 대해서 알아보자

지연로딩일 때...

지연로딩 : 엔티티를 조회할 시 데이터를 사용할 때까지 로딩을 미루는 현상

@Getter
@Entity
@ToString
@Table(name = "crew")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Crew {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "crew_id")
  private Long id;

  @OneToMany(mappedBy = "crew")
  private List<Todo> todoList;
}
@Getter
@ToString
@Entity
@Table(name = "todo")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Todo {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "todo_id")
  private Long id;
  private String content; // 내용

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "crew_id")
  private Crew crew;
}

다음과 같은 두개의 엔티티가 있다
하나의 크루에는 여러가지 할 일이 있고, 하나의 할 일은 하나의 크루 안에 속하기 때문에 이 두 엔티티의 연관관계는 1(크루):N(할 일)이다

그렇다면 CrewRepository.findAll를 사용하여 데이터를 조회해보겠다

select * from crew

이렇게 요청을 보내면

private List<Todo> todoList; 이 할일 목록 리스트는 지연로딩으로 설정이 되어있기 때문에 proxy 객체로 가지고 있다.
-> 이때까지는 쿼리가 한번만 나감

크루 한명당 몇개의 할일이 있는지 조회를 할려고 하면

for(Crew crew : Crews) {
	crew.todoList(proxy).getSize();
}

현재 지연로딩이기 때문에 proxy 객체이기 때문에 1차 캐시에서 crew_id가 1인 목록 있는지 조회를 한다

근데 없어서

select * from todo where todo_id = 1

다음과 같이 쿼리를 만들어서 데이터를 가져온다

두번째 데이터도

select * from todo where todo_id = 2

세번째 데이터도...

select * from todo where todo_id = 3

...
...
...

이렇게 해서 실행을 하면

이렇게 각각의 크루구성원의 할일 목록까지 쿼리를 만들어서 조회한다

만약 크루가 100만명이다...

select * from todo where todo_id = 1

--- (100만번 쿼리) ---

select * from todo where todo_id = 1000000

이렇게 하면 처음 크루만 전부조회하는 쿼리를 포함해서 N+1번의 쿼리가 만들어지는 것이다

엄청나게 비효율적이다

FetchType.LAZY 해결방법 (Fetch Join)

select crew.*, todo.* from crew join fetch todo

findAll() 을 할 때 이렇게 보내버리면 proxy 객체가 아닌 진짜 객체이기 때문에 DB를 거치지 않는다

즉시로딩일 때...

JPA가 연관된 엔티티를 찾고 패치 전략이 뭔지 확인을 하면 데이터를 바로 조회해온다

조회해올 때 N번의 추가쿼리가 발생해온다

즉시로딩 해결방법

즉시로딩은 사용하지 않는 것을 권장한다

그냥 지연로딩 + Fetch Join을 사용하자

Fetch Join 문제점

대부분의 경우 이렇게 Fetch Join을 사용하여 해결하면 되지만 Fetch Join도 문제점이 있다

대표적인 상황이 페이징 처리 할 때이다

이 Fetch Join 문제점 해결방안으로
@ManyToOne, @BatchSize() 가 있다

이 두가지에 대해서는 다음에 시간날 때 한번 작성해보도록 하겠다

profile
Backend Developer

0개의 댓글