์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
Cursor ๊ฐ๋ ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ์๋ตํด์ค ๋ง์ง๋ง ๋ฐ์ดํฐ ๊ธฐ์ค์ผ๋ก ๋ค์ n๊ฐ ์์ฒญ/์๋ต
offset ๊ธฐ๋ฐ ์ฟผ๋ฆฌ๊ฐ ๋ค์๊ณผ ๊ฐ๋ค๋ฉด
SELECT * FROM items WHERE ์กฐ๊ฑด๋ฌธ ORDER BY id DESC OFFSET ํ์ด์ง๋ฒํธ LIMIT ํ์ด์ง์ฌ์ด์ฆ
cursor ๊ธฐ๋ฐ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
SELECT * FROM items WHERE ์กฐ๊ฑด๋ฌธ AND id < ๋ง์ง๋ง์กฐํ_id ORDER BY id DESC LIMIT ํ์ด์ง์ฌ์ด์ฆ
์ง์ ์กฐํ ๊ฒฐ๊ณผ์ ๋ง์ง๋ง ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋ก ๋ค์ด๊ฐ๋๋ฐ SQL ํค์๋๊ฐ ์๋ ๊ณ ์ ๋ ์กฐ๊ฑด๋ฌธ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ๋๋ฌธ์ ์ ์ ์ฟผ๋ฆฌ๋ก์์ ํ๊ณ๊ฐ ์๋ ๊ฒ ๊ฐ๋ค. (๋ฌผ๋ก ๋์ ์ฟผ๋ฆฌ์ธ QueryDSL์ ์ฌ์ฉํ๋ฉด ๋๋ค.)
controller
/**
* ๊ฒ์๋ฌผ page ์กฐํ
*
* @param cursorId
* @return CursorResult<PostDto>
*/
@GetMapping(produces = APPLICATION_JSON_VALUE)
public ApiResponse<CursorResult<PostDTO.Response>> getPosts(@RequestParam("cursorId")Interger cursorId) {
CursorResult<PostDTO.Response> page = postService.findAll(PageRequest.of(0,10), cursorId);
return ApiResponse.ok(page);
}
PageRequest.of()์ ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ๋ฌด์กฐ๊ฑด 0์ผ๋ก, ์ฆ ์ต์ด์ ํ์ด์ง๋ก ์ฒ๋ฆฌ๋ฅผ ํด์ผ ํ๋ค.
public class CursorResult<T> {
private List<T> values;
private Boolean hasNext; // ๋ค์ ํ์ด์ง๊ฐ ์๋์ง ํจ๊ป ์๋ ค์ค๋ค.
public CursorResult(List<T> values, Boolean hasNext) {
this.values = values;
this.hasNext = hasNext;
}
}
repository
public interface PostJpaRepository extends JpaRepository<Post, Long> {
Slice<Post> findAllByIdLessThanOrderByIdDesc(Long id, Pageable pageable);
Slice<Post> findAllByOrderByIdDesc(Pageable pageable); // ์ต์ด์ผ ๊ฒฝ์ฐ, cursorId๊ฐ null ์ผ ๊ฒฝ์ฐ
}
์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ํธ์ถ ๊ธฐ์ค์ด ํ๋๋ผ๋ฉด ๋ฌธ์ ์๊ฒ ์ง๋ง ์ ๋ ฌ ์กฐ๊ฑด์ด๋ ์ํฉ๋ง๋ค ์๋ง์ ๋ฉ์๋๋ฅผ ์ ์ํ๊ณ ํธ์ถํด์ค์ผํ๊ฒ๋๋ฌธ์ ์ฝ๋๊ฐ ๊ต์ฅํ ๋๋ฌ์์ง๊ฒ์ผ๋ก ์์๋๋ค..
์ฟผ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ๋๊ฐ๋์ง ํ ์คํธ ํด๋ด ๋๋ค.
@Test
@DisplayName("pageNation ์ฟผ๋ฆฌ ํ์ธ")
public void findBySlice(){
Slice<Post> slice = postRepository.findAllByIdLessThanOrderByIdDesc(3L,PageRequest.of(0,10));
System.out.println(slice.hasNext());
}
Slice๋ก ๋ฐํ๊ฐ์ ์ฌ์ฉํ ๊ฒฝ์ฐ limit์ผ๋ก 'ํ์ด์ง ์ฌ์ด์ฆ + 1'๊ฐ ๋ ๋ผ๊ฐ๊ธฐ ๋๋ฌธ์ ๋ค์ ๋ฐ์ดํฐ์ ์ ๋ฌด๋ฅผ ํ์ธํ ์ ์๋ค.
ํ๋ก์ ํธ์ ์ ์ฉํ ๋์ ์ฟผ๋ฆฌ
@Override
public Slice<OrderByStoreResponse> findBy(String storeId, OrderStatus orderStatus,
String cursorOrderId, Pageable pageable) {
List<OrderByStoreResponse> result = query.select(order)
.from(order)
.leftJoin(order.orderItems, orderItem)
.leftJoin(orderItem.item, item)
.leftJoin(orderItem.customOption, customOption)
.leftJoin(user).on(user.id.eq(order.userId))
.where(
generateCursorId(cursorOrderId, pageable.getSort()),
order.storeId.eq(storeId),
order.orderStatus.eq(orderStatus)
)
.limit(pageable.getPageSize() + 1)
.orderBy(getOrder(pageable)) // ์ ๋ ฌ ๋ฐฉ์์ ๊ตฌํ๋ ๋ฉ์๋
.orderBy(order.id.desc());
return SliceUtil.toSlice(result, pageable);
}
private BooleanExpression generateCursorId(String cursorOrderId, Sort sort) {
if (cursorOrderId == null) {
return null;
}
return order.id.lt(cursorOrderId);
}
fetch join์ ํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ์ด๋ค. id๊ฐ null์ผ ๊ฒฝ์ฐ ํด๋น ์กฐ๊ฑด์ ์๋์ผ๋ก ์ ์ฉ์ด ์ ์ธ๋๊ธฐ๋๋ฌธ์ ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ฅผ ์งค ํ์๊ฐ ์์ด์ก๋ค.
Cursor ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ ๋ฌธ์ ์
cursor ๋ฐ์ดํฐ๊ฐ ์ ๋ํฌํ์ง ์์ ์ ์๋ค. (๋ ์ง ๊ฐ์)
→ ๋ฐ์ดํฐ ๋๋ฝ์ด ๋ฐ์ํ ์ ์๋ค.
→ cursor ๋ฐ์ดํฐ + ์ ๋ํฌ id๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ฉด ํด๊ฒฐ!
์ด๋ or ์ฐ์ฐ์์ ๊ฒฝ์ฐ ์ธ๋ฑ์ค๋ฅผ ํ์ง์์ ์ ์์ผ๋ ์ฃผ์ํ๋ค.
reference.
https://wonyong-jang.github.io/database/2020/09/06/DB-Pagination.html
์๋ชป๋ ์ ๋ณด๊ฐ ์๋ค๋ฉด ๋๊ธ์ ํตํด ์๋ ค์ฃผ์ธ์. ๊ฐ์ฌํฉ๋๋ค.
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] OpenFeign์ ์ด์ฉํ์ฌ API ํต์ ํด๋ณด๊ธฐ (feat. ๊ณ ์์ด ์ฌ์ง ๊ฒ์) (2) | 2023.04.25 |
---|---|
[Spring] Filter์ Interceptor ์ฐจ์ด (2) | 2023.04.22 |
[JPA] ์คํ์ ๊ธฐ๋ฐ Pagenation ๊ตฌํํ๊ธฐ (0) | 2023.03.01 |
[Spring] Spring MVC (0) | 2023.02.18 |
[Spring] Request ์ด๋ ธํ ์ด์ (parameter mapping) (2) | 2022.12.01 |
๋๊ธ