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