N + 1 문제에 대해서는 아래에 정리해 두었다.
[Spring] N + 1 문제
N+1 문제란? N+1 문제란, 하나의 쿼리(1)를 실행한 이후에, 연관된 엔티티를 가져오기 위해 추가로 N개의 쿼리가 실행되는 현상이다.이로 인해 총 1 + N개의 쿼리가 실행되어 성능 저하가 발생한다.
sunm2n.tistory.com
package com.dosion.noisense.module.comment.entity;
import com.dosion.noisense.common.entity.BaseEntity;
import com.dosion.noisense.module.board.entity.Board;
import com.dosion.noisense.module.user.entity.Users;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "comment")
public class Comment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id")
private Long id;
@Column(name = "content", nullable = false, length = 500)
private String content;
/**
* Board 연관관계
* 게시글 삭제 시 댓글도 함께 삭제되도록 CASCADE 설정
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private Board board;
/**
* Users 연관관계
* 유저 삭제 시 댓글도 함께 삭제되도록 CASCADE 설정
* 필요에 따라 CASCADE 대신 NULL 처리 방식으로 변경 가능
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private Users user;
/**
* 유저의 닉네임을 편하게 얻기 위한 Getter
*/
public String getNickname() {
return user != null ? user.getNickname() : null;
}
}
다음은 내 comment 엔티티에 관련된 코드이다.
@ManyToOne(fetch = FetchType.LAZY)
private Users user;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
comment 엔티티는 다음과 같이 두 연관 필드를 가지고 있다.
public String getNickname() {
return user != null ? user.getNickname() : null;
}
그리고 toDto() 내부에서 user.getNickname()에 접근하고 있는 게 확실하다.
이 메서드는 DTO 변환 시 사용될 가능성이 매우 높다.
따라서 findByBoardId()로 댓글 리스트를 불러온 후, 각 댓글에 대해 .getUser().getNickname()이 호출되면
댓글 N개당 추가 쿼리 N번 발생 → N+1 문제 발생한다.
예시를 보자
예를 들어 댓글이 3개 있을 때 다음과 같은 쿼리가 실행된다.
- select * from comment where board_id = ?
- select * from users where user_id = ? ← 1번째 댓글
- select * from users where user_id = ? ← 2번째 댓글
- select * from users where user_id = ? ← 3번째 댓글
총 4번의 쿼리 발생 (1 + N) → N+1 문제이다.
EntityGraph 방법을 사용할 것이다.
장점
1. JPA 표준 기능이며 깔끔하고 명시적
@EntityGraph는 JPA 표준 사양입이다.
→ Spring Data JPA에서 지원하며 별도 JPQL 없이 메서드 이름 기반 쿼리와 함께 쓸 수 있다.
@EntityGraph(attributePaths = {"user"})
List<Comment> findByBoardId(Long boardId);
가독성도 좋고 JOIN FETCH처럼 JPQL을 작성할 필요가 없다.
2. 지연 로딩(LAZY)의 이점은 유지하면서, N+1 문제는 제거
기본적으로 엔티티는 LAZY로 설정해두고, 필요한 경우에만 fetch graph로 가져오자는 철학과 잘 맞는다.
즉, 평소에는 성능 최적화를 위해 LAZY를 유지하고, 딱 필요한 서비스 로직에서만 EntityGraph로 fetch join을 걸 수 있다.
3. 유지보수성과 재사용성이 뛰어남
여러 메서드에서 동일한 fetch 그래프를 재사용할 수 있고,
필요시 @NamedEntityGraph로 재사용 가능하게 관리할 수 있다.
4. 동적 쿼리(QueryDSL)과도 잘 어울림
QuerydslJpaPredicateExecutor를 함께 사용할 때도 EntityGraph가 연동 가능하며,
복잡한 조건 필터링 + fetch join을 동시에 처리할 수 있어 유연하다.
해결과정
package com.dosion.noisense.module.comment.repository;
import com.dosion.noisense.module.comment.entity.Comment;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CommentRepository extends JpaRepository<Comment, Long> {
@EntityGraph(attributePaths = {"users", "board"})
List<Comment> findByBoardId(Long boardId);
}
@EntityGraph(attributePaths = {"users", "board"})
다음 어노테이션을 추가해서 fetch join을 해준다.
이제 이 메서드는 Comment와 연관된 Users를 한 번에 로딩한다.
→ 즉, getUser().getNickname() 호출 시 추가 쿼리가 발생하지 않는다.
private CommentDto toDto(Comment comment) {
return new CommentDto(
comment.getId(),
comment.getBoard().getId(), // ← 여기서 board 접근함 ✅
comment.getUser().getId(),
comment.getNickname(),
comment.getContent(),
comment.getCreatedDate(),
comment.getUpdatedDate()
);
}
또한 board를 넣은 이유도 comment.getBoard().getId()를 호출하고 있으므로,
→ board를 LAZY 로딩하고 있어 N개의 추가 쿼리가 발생한다.
따라서 위와 같이 users와 board 를 넣어주면 N+1 문제를 해결할 수 있다.
서비스 코드는 그대로 유지해도 된다.
'멋쟁이 사자처럼' 카테고리의 다른 글
| [종합 프로젝트] 게임 내 스텟 로직에 관련된 고민 (4) | 2025.08.07 |
|---|---|
| [종합 프로젝트] 1주차 정리 (3) | 2025.08.06 |
| [멋사 백엔드 부트캠프] 게시글 자주 나오는 단어 추출 기능 구현 (5) | 2025.07.18 |
| [멋사 백엔드 부트캠프] 게시판 기능 구현 (2) | 2025.07.11 |
| [멋사 백엔드 부트캠프] comment controller, dto 만들기 (0) | 2025.07.10 |