본문 바로가기
개발/JPA

[JPA] N+1 해결하기

by devhooney 2023. 3. 7.
728x90

N+1은 한 번의 쿼리를 요청했는데 여러 번 쿼리가 요청되는 현상을 말한다.(1+N)

 

Book 도메인에 아래와 같이 연관관계가 있었고, (각각 양방향 연관관계)

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "book_id")
    private Long id;
    
    private String bookNm;  // 도서명
    
    ...

    @OneToMany(mappedBy = "book", fetch = FetchType.LAZY,
            cascade = CascadeType.ALL , orphanRemoval = true)
    private List<Archive> archiveList = new ArrayList<>();
    
    @OneToMany(mappedBy = "book", fetch = FetchType.LAZY,
            cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Video> videoList = new ArrayList<>();

 

아래와 같이 데이터를 불러오도록 작성하였다.

JPAQuery<Book> jpaQuery = query.selectFrom(book).where(builder);
List<Book> fetch = jpaQuery.limit(pageable.getPageSize()).offset(pageable.getOffset()).fetch();

 

화면은 thymeleaf를 사용중인데, 아래처럼 book에서 archiveList의 사이즈가 0보다 클 때 링크를 띄우고 있었다.

    <td th:if="${#lists.size(book.archiveList) > 0}">
    	<a class="btn btn-ouline-mod btn-sm" th:href="@{/book/{id}(id=${li.id})}">이동</a>
    </td>

 

이러면 연관관계로 인해 archive를 가져오는 쿼리 요청이 발생한다.(Open-In-View는 true)

여기까지는 그렇다 쳐도,

Book이 여러개 일 경우 Book의 갯수 만큼 archive 쿼리 요청이 들어가서 나는 20번이 추가로 쿼리가 발생됐다.

 

먼저 여기서 문제는

DTO 사용 없이 도메인을 그대로 화면까지 보낸 것이었고,

화면에서 굳이 arvhice 전체를 사용할 일이 없기 때문에 수정이 필요했다.

 

이를 해결하기 위해

DTO 생성

    private Long id;
    private String bookNm;  // 도서명
	...
    private Long videoCount;
    private Long archiveCount;

 

QueryProjection을 사용하여 DTO 생성자 매핑

JPAQuery<BookResponseDto> jpaQuery = query
                .select(Projections.constructor(BookList.class,
                        book.id,
                        book.bookNm,
                        video.id.count().as("videoCount"),
                        archive.id.count().as("archiveCount")
                )).from(book)
                .leftJoin(video).on(book.id.eq(video.book.id))
                .leftJoin(archive).on(book.id.eq(archive.book.id))
                .where(builder)
                .groupBy(book.id);
                
List<Book> fetch = jpaQuery.limit(pageable.getPageSize()).offset(pageable.getOffset()).fetch();

 

화면 수정

<td th:if="${li.archiveCount > 0}">
	<a class="btn btn-ouline-mod btn-sm" th:href="@{/book/{id}(id=${li.id})}">이동</a>
</td>

 

이렇게 N+1 문제를 해결했다.

 

 

처음부터 이런 문제를 안만날 수 있었지만, 당시 개발할 때 임시로 이렇게 하고 추후 수정해야지 하고 뒤늦게 수정한 케이스였다. 

이런건 애초에 개발을 잘 해놓으면 되는건데...

반성

 

 

728x90