티스토리 뷰

1일 1삽질

JPAN+1 예제

sayho 2022. 7. 24. 15:51
N+1 상황 (뇌피셜)
  • 일반적인 게시판 (제목을 클릭하는,,) 은 게시글과 댓글을 한번에 가져올 필요가 없다 
    • 게시글 제목을 클릭을 하면 id 를 통해 해당 id에 해당하는 게시글과 그 게시글에 달린 댓글을 가져온다. 성능상의 문제는 없음. 게시글 한개에 댓글이 달려봤자.. 얼마나 달리겠어..
  • 내가 생각한 N+1 문제가 생길 수 있는 곳. .? 인스타그램처럼 무한스크롤 형식이 문제될 것 같음. 왜냐,, 한개의 인스타 글에는 사진, 글, 댓글 정보등 한번에 많은 정보를 가져와야함.. 그래서 하나의 피드에 많은 연관 엔티티가 있고 EAGER 로 한번에 가져오려고,, 할 수 있음,, 밑에처럼,, 
@Test
    fun OnePlusNTests() {
        val response = boardRepository.findAll(대충 페이징 파라미터)
        println(response)
    }
Board - Comments
@Entity
data class Board(
    val content: String,
    @OneToMany(fetch = FetchType.EAGER)
    val commentList: MutableList<Comment> = mutableListOf()
)

@Entity
data class Comment(
    val comment: String,
    @ManyToOne
    val board: Board
)

 

board 2개, board id가 1인 게시글의 댓글 리스트에 2개, board id가 2인 게시글의 댓글 리스트에 1개 이렇게 데이터 값을 넣고

boardRepository에서 findAll 을 호출하니 쿼리문이 1개가 아닌 3개가 나왔다.

먼저 Board 를 조회를 하고, 게시글 수(N) 만큼 comment 테이블을 조회하는 쿼리문이 실행되는 것을 볼 수 있다.

JOIN 을 이용하면 쿼리문 하나로 가지고 올 수 있는데 N+1 만큼 실행되니까 데이터가 많다면 성능 문제가 생길 수 있다.

Hibernate: 
    select
        board0_.id as id1_0_,
        board0_.crated_date as crated_d2_0_,
        board0_.last_modified_date as last_mod3_0_,
        board0_.content as content4_0_ 
    from
        board board0_
Hibernate: 
    select
        commentlis0_.board_id as board_id1_1_0_,
        commentlis0_.comment_list_id as comment_2_1_0_,
        comment1_.id as id1_2_1_,
        comment1_.crated_date as crated_d2_2_1_,
        comment1_.last_modified_date as last_mod3_2_1_,
        comment1_.board_id as board_id5_2_1_,
        comment1_.comment as comment4_2_1_,
        board2_.id as id1_0_2_,
        board2_.crated_date as crated_d2_0_2_,
        board2_.last_modified_date as last_mod3_0_2_,
        board2_.content as content4_0_2_ 
    from
        board_comment_list commentlis0_ 
    inner join
        comment comment1_ 
            on commentlis0_.comment_list_id=comment1_.id 
    left outer join
        board board2_ 
            on comment1_.board_id=board2_.id 
    where
        commentlis0_.board_id=?
Hibernate: 
    select
        commentlis0_.board_id as board_id1_1_0_,
        commentlis0_.comment_list_id as comment_2_1_0_,
        comment1_.id as id1_2_1_,
        comment1_.crated_date as crated_d2_2_1_,
        comment1_.last_modified_date as last_mod3_2_1_,
        comment1_.board_id as board_id5_2_1_,
        comment1_.comment as comment4_2_1_,
        board2_.id as id1_0_2_,
        board2_.crated_date as crated_d2_0_2_,
        board2_.last_modified_date as last_mod3_0_2_,
        board2_.content as content4_0_2_ 
    from
        board_comment_list commentlis0_ 
    inner join
        comment comment1_ 
            on commentlis0_.comment_list_id=comment1_.id 
    left outer join
        board board2_ 
            on comment1_.board_id=board2_.id 
    where
        commentlis0_.board_id=?
[Board(content='boards'), Board(content='boards2')]

N+1 문제를 해결하는 방법은 다음과같다.

 

QueryDsl 을 이용한 Fetch JOIN 

QueryDsl을 이용해서 Fetch JOIN 을 하는것으로 해결할 수 있다. 

class BoardRepositorySupportImpl(
    private val queryFactory: JPAQueryFactory
): QuerydslRepositorySupport(Board::class.java), BoardRepositorySupport {
    override fun findAllBoardFetchJoin(pageable: Pageable): List<Board> {
        return queryFactory.selectFrom(QBoard.board)
            .leftJoin(board.commentList, comment1)
            .fetchJoin()
            .orderBy(OrderSpecifier(Order.DESC, QBoard.board.cratedDate))
            .fetch()
    }
}

fetch는 리스트로 결과를 반환하기 위해서 사용

 

일반 join과 Fetch Join 차이점
  • fetch join : 조회 주체가 되는 엔티티 외에 연관 엔티티도 함께 SELECT 하여 영속화
  • 일반 join : 연관 엔티티에 join 을 걸어도 조회 주체 엔티티만 조회해서 영속화

여튼 fetchjoin 으로 바꾼 후 해당 메서드로 게시글 전체 조회를 한 결과 다음과 같은 쿼리가 실행됨

select
        board0_.id as id1_0_0_,
        comment2_.id as id1_2_1_,
        board0_.crated_date as crated_d2_0_0_,
        board0_.last_modified_date as last_mod3_0_0_,
        board0_.content as content4_0_0_,
        comment2_.crated_date as crated_d2_2_1_,
        comment2_.last_modified_date as last_mod3_2_1_,
        comment2_.board_id as board_id5_2_1_,
        comment2_.comment as comment4_2_1_,
        commentlis1_.board_id as board_id1_1_0__,
        commentlis1_.comment_list_id as comment_2_1_0__ 
    from
        board board0_ 
    left outer join
        board_comment_list commentlis1_ 
            on board0_.id=commentlis1_.board_id 
    left outer join
        comment comment2_ 
            on commentlis1_.comment_list_id=comment2_.id 
    order by
        board0_.crated_date desc

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함