[Spring Boot] JPA 쿼리 최적화

2024. 10. 4. 21:18·Back-End/Spring Boot
728x90

 

들어가며

 

2024년 7월 말부터 10월 초까지 약 3개월 간 진행했던 프로젝트에 대해 정리할 만한 내용을 블로그에 기재하기로 했다. 프로젝트명은 InConcert로, 공연 소식을 확인하고 동행을 구하는 서비스이다. 크게 정리할 내용은 다음과 같다.

  • 쿼리 최적화 ➡️ 이번 게시글 주제!
  • 공연 정보 스크래핑 (비동기 처리)
  • 인기 공연 순위 알고리즘

이 글에서는 쿼리 최적화 과정에 대해 설명하려고 한다.

 

페이지 구성

우선 메인 페이지를 살펴보면 카테고리 별로 게시물을 출력하고, 상단에는 인기 공연을 보여주고 있다.

메인 페이지

 

 

문제 상황

메인 페이지를 조회했을 때 글을 불러오면서 화면상에 보이는 게시물뿐만 아니라 전체 게시물을 불러오는 문제가 있어 html의 코드를 수정하였다.

N+1 문제가 발생하는 상황

공연 정보를 스크래핑하는 경우를 고려한다면 게시물이 적어도 500개가 작성되는 상황이다. 이 경우 쿼리가 500개가 나가는 문제가 발생한다. 우선 메인 페이지에서 조회할 때 화면에 보이는 게시물의 개수만 출력하고자 했다.

 

기존 코드
<div class="post-list" th:each="post : ${infoPosts}">
	...
</div>

 

수정한 코드
<div class="post-list" th:if="${idx.index} < 8" th:each="post, idx : ${infoPosts}">
	...
</div>

 

메인 페이지에서는 각 카테고리 별로 8개만 글을 조회하기 때문에 html 코드에서 인덱스를 사용하는 형식으로 코드를 수정하였다.

 

 

인덱스를 사용해서 문제점이 해결 되었나?

 

결론부터 말하면 아니었다. 엔티티의 구성이 Post, Category, PostCategory로 나뉘어 있었는데, 각 엔티티는 Lazy Loading으로 엮여 있는 상황이다. Repository에서 이들을 연결할 때는 JOIN이 아닌 JOIN FETCH로 조회하도록 하였다. 기존 코드를 보면 다음과 같다.

 

기존 코드
@Repository
public interface InfoRepository extends JpaRepository<Post, Long> {
    // 카테고리가 info인 post 조회
    @Query("SELECT p FROM Post p " +
            "JOIN FETCH p.postCategory pc " +
            "JOIN FETCH pc.category c " +
            "WHERE c.title = 'info'")
    List<Post> findPostsByCategoryTitleInfo();
    
    // 다른 메소드 생략
}

 

대표로 InfoRepository를 살펴보도록 하자. JPQL을 사용하여 postCategory와 category가 JOIN FETCH로 작성되어 있는 것을 볼 수 있다. Post 엔티티에는 댓글과 좋아요 필드가 List 형식으로 있는데, 이 필드 또한 Lazy Loading 되어 있어 N+1 문제가 발생할 가능성이 있었다. N+1 문제를 해결하는 데는 다른 연관된 필드 또한 JOIN FETCH로 작성하거나, Batch Size를 설정하는 방법이 있지만, DTO를 사용해 원하는 필드만 전달하도록 작성하였다.

💡 N+1 문제 해결 방법
1. 연관된 필드에 대해 JOIN FETCH 작성
2. Batch Size 설정
3. DTO를 이용하여 원하는 필드만 조회 ➡️ 해당 방법 이용!

 

 

해결 과정

PostDTO
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PostDTO {
    private Long id;
    private String title;
    private Category category;
    private String categoryTitle;
    private PostCategory postCategory;
    private String postCategoryTitle;
    private LocalDate endDate;
    private int matchCount;
    private String content;
    private String thumbnailUrl;
    private String username;
    private String nickname;
    private int viewCount;
    private int likeCount;
    private int commentCount;
    private List<Comment> comments;
    private boolean isNew;
    private LocalDateTime createdAt;
    private User user;
    
    // 이하 메소드 생략
}

 

Repository 수정
@Repository
public interface InfoRepository extends JpaRepository<Post, Long> {
    // 카테고리가 info인 post 조회 (페이징 처리 추가)
    @Query("SELECT new com.inconcert.domain.post.dto.PostDTO(p.id, p.title, c.title, pc.title, p.thumbnailUrl, u.nickname, " +
            "p.viewCount, SIZE(p.likes), SIZE(p.comments), " +
            "CASE WHEN TIMESTAMPDIFF(HOUR, p.createdAt, CURRENT_TIMESTAMP) < 24 THEN true ELSE false END, p.createdAt) " +
            "FROM Post p " +
            "JOIN p.postCategory pc " +
            "JOIN pc.category c " +
            "JOIN p.user u " +
            "WHERE c.title = 'info' " +
            "ORDER BY p.createdAt DESC")
    List<PostDTO> findPostsByCategoryTitle(Pageable pageable);
    
    // 다른 메소드 생략
}

 

페이징 처리와 DTO를 이용하여 카테고리가 info인 게시물을 8개만 조회하도록 수정하였다. DTO를 이용하여 쿼리를 작성할 때의 장점은 원하는 필드만 DTO로 조회하여 성능을 최적화할 수가 있다. 실제로 500개가 나갔던 쿼리가 카테고리 별로 1개씩만 조회하여 메인 페이지에 접속할 때 쿼리가 확연하게 준 것을 확인할 수 있었다.

메인 페이지 접속 시

이후에 다시 N+1 문제가 발생하게 된다면 그때는 Batch Size를 설정하는 등 다른 방법으로도 해결해보고 싶다고 느꼈다.

 

 

728x90
저작자표시 비영리 변경금지 (새창열림)

'Back-End > Spring Boot' 카테고리의 다른 글

[Spring Boot] Spring AOP  (0) 2024.10.10
[Spring Boot] 스크래핑 비동기 처리  (6) 2024.10.09
[Spring Boot] N+1 문제  (1) 2024.10.05
[Spring Boot] Setter vs Builder  (0) 2024.03.28
[Spring Boot] Logging 처리  (0) 2023.11.27
'Back-End/Spring Boot' 카테고리의 다른 글
  • [Spring Boot] 스크래핑 비동기 처리
  • [Spring Boot] N+1 문제
  • [Spring Boot] Setter vs Builder
  • [Spring Boot] Logging 처리
hxxzz
hxxzz
개발새발 안 되게 개발 노력 중
  • hxxzz
    개발새발
    hxxzz
  • 전체
    오늘
    어제
    • 분류 전체보기 (104)
      • Java (3)
      • Back-End (9)
        • Spring Boot (7)
        • DevOps (1)
        • Redis (1)
      • Computer Scrience (4)
        • Data Structrue (4)
        • Algorithm (0)
      • SQLD (3)
      • 코딩테스트 연습 (85)
        • Programmers (30)
        • 백준 (15)
        • etc. (0)
        • 99클럽 (40)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    jpa
    Spring Boot
    SpringBoot
    BFS
    Stack
    프로그래머스
    스택
    자료구조
    개발자 취업
    N+1 문제
    LeetCode
    java
    til
    백준
    dfs
    redission
    SQLD
    코딩테스트 준비
    99클럽
    SQL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
hxxzz
[Spring Boot] JPA 쿼리 최적화
상단으로

티스토리툴바