Spring/Spring 문법

N + 1 문제 해결, JPQL과 QueryDsl에서 JOIN FETCH

열심히 해 2024. 11. 26. 14:28

서비스단에서 아래와 같은 코드가 있다고 해봅시다.

public List<CommentResponse> getComments(long todoId) {
    List<Comment> commentList = commentRepository.findByTodoIdWithUser(todoId);

    List<CommentResponse> dtoList = new ArrayList<>();
    for (Comment comment : commentList) {
        User user = comment.getUser();
        CommentResponse dto = new CommentResponse(
                comment.getId(),
                comment.getContents(),
                new UserResponse(user.getId(), user.getEmail())
        );
        dtoList.add(dto);
    }
    return dtoList;
}

 

 

for 문을 돌면서 ``User user = comment.getUser();`` comment에 있는 User을 하나 하나 꺼내오고 있습니다.

 

이때 N+1 문제가 발생할 수 있습니다.

N+1 문제는 특정 객체를 대상으로 수행한 쿼리 -
**List<Comment> commentList = commentRepository.findByTodoIdWithUser(todoId);** -

해당 객체가 가지고 있는 연관관계 또한 조회하게 되면서 N번의 추가적인 쿼리가 발생하는 것을 말합니다.

 

 

 

이를 방지 또는 해결하기 위해선 어떻게 해야할까요 ??

 

JPQL

// Repository

@Query("SELECT c FROM Comment c JOIN c.user WHERE c.todo.id = :todoId")
List<Comment> findByTodoIdWithUser(@Param("todoId") Long todoId);

// 위를 아래처럼 바꿉니다.

@Query("SELECT c FROM Comment c JOIN FETCH c.user WHERE c.todo.id = :todoId")
List<Comment> findByTodoIdWithUser(@Param("todoId") long todoId);

 

 

 

QueryDsl

// CommentRepositoryQueryImpl

@Override
public List<Comment> findByTodoIdWithUser(Long todoId) {
    QComment comment = QComment.comment;
    QUser user = QUser.user;

    return queryFactory
            .selectFrom(comment)
            .join(comment.user, user).fetchJoin()
            .where(comment.todo.id.eq(todoId))
            .fetch();
}

 

 

 


 

 

 

>>  즉시 로딩(Fetch Join) 을 적용해 Comment와 User를 한 번에 가져오도록 처리할 수 있습니다.

 

 

즉시 로딩과 지연 로딩이란 무엇일까 ??

- 즉시 로딩: 즉시 로딩은 엔티티를 조회할 때 해당 엔티티와 연관된 모든 엔티티를 함께 조회하는 방식입니다. 연관 데이터를 캐시에 미리 올려놓기 때문에 추가적인 쿼리가 발생하지 않습니다.

- 지연 로딩: 지연 로딩은 엔티티를 조회할 때, 연관된 엔티티는 조회하지 않고 필요할 때 별도의 쿼리를 통해 로딩하는 방식입니다. 필요한 시점에만 연관된 엔티티를 로딩하므로 메모리 효율이 좋습니다.

 

 

`1 : N = Todo : Comment`


1 : N 연관관계에서 'N'의 엔티티를 조회할 때 지연 로딩이 기본값입니다. 왜냐하면 필요로 하지 않는 데이터를 찾으며 메모리를 사용하지 않고, 필요로 할 때 쿼리를 날리면 효율적이기 때문입니다. 위의 예시에서 작성자가 누군지 궁금하지 않고 Comment의 내용만 알고 싶을 수도 있습니다.

 

public List<CommentResponse> getComments(long todoId) {
    List<Comment> commentList = commentRepository.findByTodoIdWithUser(todoId);

    List<CommentResponse> dtoList = new ArrayList<>();
    for (Comment comment : commentList) {
        CommentResponse dto = new CommentResponse(
                comment.getId(),
                comment.getContents())
        );
        dtoList.add(dto);
    }
    return dtoList;
}



 

 

 

'Spring > Spring 문법' 카테고리의 다른 글

Pageable 과 PagedModel  (1) 2024.12.09
Mock 을 이용한 테스트 코드 - argument matcher  (0) 2024.11.26
AWS S3 버킷 사용하기  (0) 2024.11.25
Transaction Propagation  (0) 2024.11.25
QueryDsl - Projections 의 4가지 방식  (0) 2024.11.21