public Page<BookResponseDto> getAllBooksByCategoryOrKeyword3(String bookCategoryName, String keyword, int page) {
QBook qBook = QBook.book;
BooleanBuilder builder = new BooleanBuilder();
List<BookCategory> bookCategories = null;
if (bookCategoryName != null) {
BookCategory bookCategory = bookCategoryRepository.findByBookCategoryName(bookCategoryName);
bookCategories = saveAllCategories(bookCategory);
}
if(keyword != null)
builder.and(qBook.bookName.contains(keyword));
if(bookCategories != null)
builder.and(qBook.bookCategory.in(bookCategories));
Sort sort = Sort.by(Sort.Direction.ASC, "bookId");
Pageable pageable = PageRequest.of(page, 20, sort);
Page<BookResponseDto> bookList = bookRepository.findAll(builder, pageable).map(BookResponseDto::new);
System.out.println(bookList.getTotalElements());
return bookList;
}
기존에는 JPA의 페이징 기능을 사용하여 데이터를 조회하고 있었다.
하지만 데이터의 양이 많아지면서 페이지를 계산하기 위한 카운트 쿼리의 실행 시간이 길어지기 시작했고 이로 인해 사용자가 데이터를 조회하는데 거의 4 ~ 5 초 가까이 걸리는 상황이 발생했다.
이 문제를 해결하기 위해 슬라이스 기능을 도입하게 되었다.
슬라이스는 전체 페이지 수나 총 데이터 개수를 알 필요 없이 현재 페이지의 데이터만을 가져올 수 있는 방식으로 메모리 사용량을 줄이고 성능을 향상시킬 수 있는 장점이 있다.
새로운 슬라이스 기능 적용을 위한 Repository를 만들고
Slice<T>
를 반환하는 메소드를 만들었다.
@Repository
public interface CustomBookRepository {
Slice<Book> findAllSliceBooks(BooleanBuilder builder, Pageable pageable);
}
생성한 메소드를 Override해서 함수를 최종적으로 구현한다.
@Repository
public class CustomBookRepositoryImpl implements CustomBookRepository{
@Autowired
private JPAQueryFactory queryFactory;
@Override
public Slice<Book> findAllSliceBooks(BooleanBuilder builder, Pageable pageable) {
QBook book = QBook.book;
List<Book> results = queryFactory.selectFrom(book)
.where(builder)
.limit(pageable.getPageSize()+1)
.orderBy(book.bookId.asc())
.offset(pageable.getOffset())
.fetch();
boolean hasNext = false;
if(results.size() > pageable.getPageSize()){
results.remove(results.size()-1);
hasNext = true;
}
return new SliceImpl<>(results,pageable,hasNext);
}
}
Slice
를 반환받아 로직을 처리하도록 수정하였고 클라이언트에 필요한 데이터만을 추려서 전달하도록 했다.
public Slice<BookResponseDto> getAllBooksByCategoryOrKeywordV4(String bookCategoryName, String keyword, int page) {
QBook qBook = QBook.book;
BooleanBuilder builder = new BooleanBuilder();
List<BookCategory> bookCategories = null;
if (bookCategoryName != null) {
BookCategory bookCategory = bookCategoryRepository.findByBookCategoryName(bookCategoryName);
bookCategories = saveAllCategories(bookCategory);
}
if (keyword != null)
builder.and(qBook.bookName.contains(keyword));
if (bookCategories != null)
builder.and(qBook.bookCategory.in(bookCategories));
Sort sort = Sort.by(Sort.Direction.ASC, "bookId");
Pageable pageable = PageRequest.of(page, 20, sort);
// Slice로 변경
Slice<BookResponseDto> bookList = customBookRepository.findAllSliceBooks(builder, pageable).map(BookResponseDto::new);
System.out.println(bookList.hasNext());
return bookList;
}
클라이언트에게 Slice
의 내용물과 함께 다음 페이지 존재 여부도 함께 전달하도록 수정했다.
@GetMapping("/search/v4")
public String mySearchView4(@RequestParam(value = "bookCategoryName", required = false) String bookCategoryName,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "page", defaultValue = "0", required = false) Integer page,
Model model) {
// Slice로 변경
Slice<BookResponseDto> bookResponseDtoSlice = searchService.getAllBooksByCategoryOrKeywordV4(bookCategoryName, keyword, page);
long startTime = System.currentTimeMillis();//실행시간 측정
model.addAttribute("categories", adminCategoriesService.getAllCategories());
model.addAttribute("currentPage", page);
model.addAttribute("books", bookResponseDtoSlice.getContent());
model.addAttribute("hasNext", bookResponseDtoSlice.hasNext());
long endTime = System.currentTimeMillis();
long durationTimeSec = endTime - startTime;
System.out.println(durationTimeSec + "m/s"); // 실행시간 측정
return "/users/searchV2";
}
필요한 경우 프론트엔드에서도 Slice
의 hasNext
값을 사용하여 추가적인 데이터 로딩 여부를 결정할 수 있도록 수정했다.
<div id="paging">
<button th:if="${currentPage!=0}" onclick="goToPrePage()">Pre</button>
<button th:if="${hasNext}" onclick="goToNextPage()">Next</button>
</div>
<script th:inline="javascript">
/*<![CDATA[*/
let currentPage = [[${currentPage}]];
/*]]>*/
</script>
카운터 하는 쿼리가 나가지 않기에 성능이 4257 -> 13으로 300배 정도 성능이 향상되었다.
슬라이스를 도입한 후 데이터 조회 시간이 현저히 줄어들었다.
사용자는 더 빠르게 데이터를 조회할 수 있게 되었고 서버와 데이터베이스에 가해지는 부하도 감소했다.