들어가기 전에...
지금 부터 설명할, 두 코드 모두 전체 데이터를 가져오는데 사용할 수 있지만, 성능 면에서 차이가 있다.
첫번째 방법의 경우 query.fetch().size()를 사용하여 전체 데이터 개수를 가져온다.
이는 쿼리를 실행하여 모든 결과를 가져온 후 리스트의 크기를 반환하는 방식이다.
이렇게 하면 전체 결과를 메모리로 가져오기 때문에 가져올 데이터가 매우 큰 경우, 성능이 저하될 수 있다.
또한, fetch().size() 메서드 호출이 실제로는 모든 결과를 가져오기 때문에 성능적으로 비효율적이다.
두 번째 방법에서는 count 쿼리를 사용하여 전체 레코드 수를 가져온다.
count 쿼리는 실제 데이터를 가져오지 않고 단순히 데이터 개수만을 반환하므로, 성능상의 이점이 있다.
또한, 전체 데이터를 메모리로 가져올 필요도 없어 데이터 크기가 클 경우, 성능의 저하도 피할수 있다.
앞으로 2번째 방법으로 페이징 조회를 할때 사용해야 겠다.
그럼 이제부터 더 자세히 알아보자.
1. PageImpl< >(content, pageable, count);
PageImpl의 경우
어떠한 경우에서든지, contents쿼리 1번 + count쿼리 1번, 총 2번의 쿼리가 나가게 된다.
그리고 내가 짠 아래코드의 문제점은
1. count쿼리의 size( )를 구하기 위해 DB에서 개수를 가져오는 게 아닌 전체 결과를 메모리에 올린 후 크기를 구해
메모리에 큰 부담을 주게된다.
2. count가 long 타입도 아닌 int 타입으로 선언한것
짜고 나서 보니, 이 두가지가 가장 큰 문제였다.
그래서 좀 더 성능을 최적화 할 방법으로 뒤에 나올 2번의 방식으로 바꿔 주었다.
@Transactional(readOnly = true)
@Override
public Page<PostDto> getPostsByHashTag(HashSet<Long> idList, Pageable pageable, OrderSpecifier<?> orderSpecifier) {
List<PostDto> content = jpaQueryFactory
.select(Projections.fields(PostDto.class,
post.id.as("postId"),
post.title,
post.postContent,
post.postType,
post.postMeeting,
post.postStatus,
post.createdAt,
post.user.userId,
post.user.nickname.as("userNickname")))
.from(post)
.join(post.user, user)
.where(isEmptyPostId(idList))
.orderBy(orderSpecifier)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Post> query = jpaQueryFactory
.select(post)
.from(post)
.join(post.user, user)
.where(isEmptyPostId(idList));
int count = query.fetch().size();
return new PageImpl<>(content, pageable, count);
}
private BooleanExpression isEmptyPostId(HashSet<Long> idList) {
return !idList.isEmpty() ? post.id.in(idList) : null;
}
size=10, page=0 size=10, page=1
DB에 post entity의 개수가 18개 있다.
결과 : 4번의 쿼리 + 메모리의 부담 으로 모든 게시물을 페이징하여 조회할 수 있었다.
2. PageableExecutionUtils.getPage()
- CountQuery 최적화 : count 쿼리가 생략 가능한 경우 생략해서 처리
- 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때,
마지막 페이지 이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때 count 쿼리가 생략된다. - (offset + 컨텐츠 사이즈)를 더해서 전체 사이즈 구함
@Transactional(readOnly = true)
@Override
public Page<PostDto> getPostsByHashTag(HashSet<Long> idList, Pageable pageable,
OrderSpecifier<?> orderSpecifier) {
List<PostDto> content = jpaQueryFactory
.select(Projections.fields(PostDto.class,
post.id.as("postId"),
post.title,
post.postContent,
post.postType,
post.postMeeting,
post.postStatus,
post.createdAt,
post.user.userId,
post.user.nickname.as("userNickname")))
.from(post)
.join(post.user, user)
.where(isEmptyPostId(idList))
.orderBy(orderSpecifier)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> countQuery = jpaQueryFactory
.select(post.count())
.from(post)
.join(post.user, user)
.where(isEmptyPostId(idList));
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}
private BooleanExpression isEmptyPostId(HashSet<Long> idList) {
return !idList.isEmpty() ? post.id.in(idList) : null;
}
}
size=10, page=0 size=10, page=1
DB에 post entity의 개수가 18개 있다.
위와 같은 페이징 쿼리를 사용할 경우, page=0일 때는 1( contents 쿼리 ) + 1( count 쿼리 ) = 2번의 쿼리가 나가지만,
page=1 (마지막 페이지, 컨텐츠 사이즈 < 페이지 사이즈) 일 때는 content를 가져오기 위한 1번의 쿼리만 나가는 것을 확인할 수 있다.
(offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함, 더 정확히는 마지막 페이지 이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때)
결과 : 3번의 쿼리를 날려서 모든 게시물을 페이징하여 조회할 수 있었다.
'Side Project > Socket' 카테고리의 다른 글
List를 pageable과 PageImpl로 구현하기 - List Paging (0) | 2024.03.15 |
---|---|
List<String> 타입의 경우 Nullable 할 때 주의할 점. (0) | 2024.02.05 |
[MySQL] 테이블 데이터 다 지우기 (0) | 2024.02.05 |
@Transactional 어느 Layer에 두는게 맞을까? (2) (0) | 2024.02.04 |
[Self-Invocation]@Transactional 어느 Layer에 두는게 맞을까? (1) (0) | 2024.02.04 |