๋ธ๋ก๊ทธ๋ ์ปค๋ฎค๋ํฐ ๊ฒ์ํ์ฒ๋ผ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ์๋น์ค์์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ด๋ ค์ฃผ๋ ๊ฒ์ ๋นํจ์จ์ ์ด๋ค. ์๋ฒ์๋ ๋ถํ์ํ ๋ถํ๊ฐ ๋ฐ์ํ๊ณ ํด๋ผ์ด์ธํธ ์ญ์ ๋คํธ์ํฌ ๋ญ๋น์ ๋ ๋๋ง ์ง์ฐ์ ๊ฒช์ ์ ์๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋ก ํ์ด์ง ์ฒ๋ฆฌ(Pagination)๋ฅผ ์ฌ์ฉํ๋ค. ๋ฐ์ดํฐ๋ฅผ ์ผ์ ๋จ์๋ก ๋๋์ด ์ ๋ฌํ๋ฉด ํด๋ผ์ด์ธํธ๋ ํ์ํ ํ์ด์ง๋ง ์์ฒญํด ํจ์จ์ ์ผ๋ก ํ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ํ์ํ ์ ์๋ค.
Spring Boot์์๋ Pageable์ ํ์ฉํด ๋น๊ต์ ๊ฐ๋จํ๊ฒ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์๋ค.
Pageable์ Spring Data์์ ์ ๊ณตํ๋ ํ์ด์ง ์ ๋ณด๋ฅผ ๋ด๋ ์ธํฐํ์ด์ค๋ก ํ์ด์ง ๋ฒํธ, ํ์ด์ง ํฌ๊ธฐ, ์ ๋ ฌ ์ ๋ณด ๋ฑ์ ํฌํจํ๋ค.
Spring Boot์์๋ Pageable ๊ฐ์ฒด๋ฅผ ์ปจํธ๋กค๋ฌ ํ๋ผ๋ฏธํฐ๋ก ์ ์ธํ๋ฉด ํด๋ผ์ด์ธํธ์ ํ์ด์ง ์์ฒญ์ ์๋์ผ๋ก ๋ฐ์ธ๋ฉํด์ค๋ค.
[ํด๋ผ์ด์ธํธ ์์ฒญ ์์]
GET /posts?page=0&size=10&sort=createdAt,descโ
@GetMapping
public PageResponse<PostListResponse> getAllPosts(
@PageableDefault(
page = 0,
size = 10,
sort= { "createdAt", "id" },
direction = Sort.Direction.DESC
) Pageable pageable
) {
Page<PostListResponse> page = userPostService.getAllPosts(pageable)
.map(PostListResponse::from);
return PageResponse.from(page);
}
Pageable์ ๊ธฐ๋ณธ์ ์ผ๋ก page, size, sort๋ผ๋ ์์ฒญ ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ๊ธฐ์ค์ผ๋ก ๋ฐ์ธ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์์ ๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ ๋ฌํ๋ฉด ์ธ์๋์ง ์์ผ๋ฉฐ ํ์ํ๋ค๋ฉด spring.data.web.pageable ์ค์ ์ ํตํด ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ๋ณ๊ฒฝํ ์ ์๋ค. sort ํ๋ผ๋ฏธํฐ๋ ์ฌ๋ฌ ๊ฐ ์ ๋ฌํ ์ ์์ผ๋ฉฐ ์ ๋ฌ๋ ์์๋๋ก ์ ๋ ฌ ์กฐ๊ฑด์ด ์ ์ฉ๋๋ค.
[createdAt DESC -> title ASC ์์๋ก ์ ์ฉ]
GET /posts?sort=createdAt,desc&sort=title,asc
@PageableDefault๋ Pageable์ ๊ธฐ๋ณธ๊ฐ์ ์ค์ ํ ๋ ์ฌ์ฉํ๋ค. ์ค์ ๊ฐ๋ฅํ ์ฃผ์ ํ๋ผ๋ฏธํฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
page: ๊ธฐ๋ณธ ํ์ด์ง ๋ฒํธ (์ฃผ๋ก 0-based) size: ํ ํ์ด์ง์ ์กฐํํ ๋ฐ์ดํฐ ๊ฐ์ sort: ์ ๋ ฌ ๊ธฐ์ค ํ๋ direction: ์ ๋ ฌ ๋ฐฉํฅ (ASC / DESC)
์ดํ ์ปจํธ๋กค๋ฌ์์ ์ ๋ฌ๋ฐ์ Pageable ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์๋น์ค ๊ณ์ธต์ผ๋ก ์ ๋ฌํ์ฌ ํ์ด์ง ์กฐํ ๋ก์ง์ ๊ตฌํํ์๋ค.
public Page<PostListDto> getAllPosts(Pageable pageable) {
Page<Post> page = postRepository.findVisiblePosts(pageable);
return page.map(PostListDto::of);
}
@EntityGraph(attributePaths = { "category", "medias" })
@Query("SELECT p FROM Post p WHERE p.status = 'PUBLISHED'")
Page<Post> findAllPublished(Pageable pageable);
findAllPublished()๋ ๋ ํฌ์งํ ๋ฆฌ์์ @Query๋ฅผ ์ฌ์ฉํ์ฌ ์ง์ ์์ฑํ ์ปค์คํ
JPA ์ฟผ๋ฆฌ ๋ฉ์๋์ด๋ฉฐ @EntityGraph๋ฅผ ํตํด ์ฐ๊ด ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํ๋๋ก ์ค์ ํ์๋ค. (์ฐธ๊ณ )
๋ ํฌ์งํ ๋ฆฌ ๋ฉ์๋์ Pageable์ด ํฌํจ๋๋ฉด Spring Data JPA๋ ๋ค์๊ณผ ๊ฐ์ด ๋์ํ๋ค.
1. ๊ธฐ๋ณธ ์กฐํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
2. ์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํ count query๋ฅผ ์คํํ๋ค.
3. Pageable ์ ๋ณด(page, size, sort)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก SQL์ LIMIT๊ณผ OFFSET์ ์ ์ฉํ๋ค.
[์ค์ SQL ๋ณํ ์์]
select
p1_0.id,
p1_0.title,
p1_0.slug,
p1_0.content,
-- ์ดํ ์๋ต
from
post p1_0
left join
category c1_0
on c1_0.id=p1_0.category_id
left join
media m1_0
on p1_0.id=m1_0.post_idโ
where
p1_0.status='PUBLISHED'
order by
p1_0.created_at desc,
p1_0.id desc
fetch
first ? rows only
fetch first ? rows only ๊ตฌ๋ฌธ์ PostgreSQL์์ ์ฌ์ฉํ๋ ํ์ด์ง ๋ฌธ๋ฒ์ผ๋ก MySQL์ limit ? ๊ณผ ๋์ผํ ์ญํ ์ ํ๋ค. ์ฆ ์์์๋ถํฐ ์ง์ ๋ ๊ฐ์๋งํผ์ ํ์ ์กฐํํ๋ค๋ ์๋ฏธ์ด๋ค.
๋ํ ์ฒซ ํ์ด์ง๋ฅผ ์์ฒญํ๋ ๊ฒฝ์ฐ OFFSET ๊ฐ์ ์๋ต๋ ์ ์๋ค.
Spring Data JPA๋ Pageable ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ด์ง SQL์ ์๋ ์์ฑํ๊ธฐ ๋๋ฌธ์ SQL์ ์ง์ ์์ฑํ์ง ์๊ณ ๋ ํจ์จ์ ์ธ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์๋ค.
Spring Data JPA์์๋ ํ์ด์ง ์กฐํ ์ ๋ฐํ ํ์
์ ์ ํํ ์ ์๋ค.
Page
์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์์ ํ์ด์ง ์ ๋ณด๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด์ด๋ค. ์ด ํ์ด์ง ์, ํ์ฌ ํ์ด์ง ๋ฒํธ, ํ์ด์ง ํฌ๊ธฐ๋ฅผ ํ์ธํ ์ ์๋ค.
์ผ๋ฐ์ ์ธ ๊ฒ์ํ ์กฐํ์ ์ ํฉํ์ง๋ง ๋ฐ์ดํฐ ์ ์ฒด ๊ฐ์๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํ count query๊ฐ ์ถ๊ฐ๋ก ์คํ๋ ์ ์๋ค.
Slice Slice๋ ๋ค์ ํ์ด์ง ์กด์ฌ ์ฌ๋ถ๋ง ํ์ธํ๋ ๋ฐํ ํ์
์ด๋ค. count query๋ฅผ ์คํํ์ง ์๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์ธก๋ฉด์์ Page๋ณด๋ค ์ ๋ฆฌํ ์ ์๋ค.
์ ์ฒด ํ์ด์ง ์๊ฐ ํ์ํ์ง ์์ ๋ฌดํ ์คํฌ๋กค UI์ ์ ํฉํ๋ค.
List List๋ ํ์ด์ง ์ ๋ณด๋ฅผ ํฌํจํ์ง ์๊ณ ๋ฐ์ดํฐ ๋ชฉ๋ก๋ง ๋ฐํํ๋ค. ๋จ์ ์กฐํ ๋๋ ๋ณ๋์ ํ์ด์ง ์ ๋ณด๊ฐ ํ์ํ์ง ์์ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ค.
์ปจํธ๋กค๋ฌ์์ ๋ฐํ ์ Page<T>๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ์ง ์๊ณ ์๋ต DTO๋ฅผ ๋ณ๋๋ก ์ ์ํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ค. Page ๊ฐ์ฒด์๋ ํ์ด์ง ๊ด๋ จ ๋ฉํ ์ ๋ณด๋ฟ๋ง ์๋๋ผ ๋ค์ํ ๋ด๋ถ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์๋ค. ๋ฐ๋ผ์ ๋ถํ์ํ ๋ด๋ถ ์ ๋ณด ๋
ธ์ถ์ ๋ฐฉ์งํ๊ณ ํด๋ผ์ด์ธํธ์๊ฒ ํ์ํ ๋ฐ์ดํฐ๋ง ์ ๋ฌํ๊ธฐ ์ํด ์๋ต DTO๋ฅผ ๋ถ๋ฆฌํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
๋ํ API ์๋ต ๊ตฌ์กฐ๋ฅผ ๋ช
ํํ๊ฒ ๊ด๋ฆฌํ ์ ์์ด ์ ์ง๋ณด์ ์ธก๋ฉด์์๋ ์ ๋ฆฌํ๋ค.
Pageable์ ๊ธฐ๋ณธ์ ์ผ๋ก Offset ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ค. Offset ๊ธฐ๋ฐ Pagination์ ํ์ด์ง ๋ฒํธ์ offset์ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ์์ด๋ค. ํ์ง๋ง ํ์ด์ง ๋ฒํธ๊ฐ ์ปค์ง์๋ก ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ฑด๋๋ฐ์ด์ผ ํ๋ฏ๋ก ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ์ฑ๋ฅ์ด ์ ํ๋ ์ ์๋ค.
๋ฐ๋ฉด Cursor ๊ธฐ๋ฐ Pagination์ ๋ง์ง๋ง์ผ๋ก ์กฐํํ ๋ฐ์ดํฐ์ ์์น๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ์์ด๋ค. ๋ํ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ Cursor๋ก ์ฌ์ฉํ ์ ์๋ค.
Cursor Pagination์ Offset ๋ฐฉ์๋ณด๋ค ์ฑ๋ฅ์ด ์์ ์ ์ด๋ฉฐ ๋๋ ๋ฐ์ดํฐ ์กฐํ๋ ๋ฌดํ ์คํฌ๋กค UI์ ์ ํฉํ๋ค.
๋ค๋ง ๊ตฌํ์ด ์๋์ ์ผ๋ก ๋ณต์กํ๊ณ ์ ๋ ฌ ๊ธฐ์ค์ด ์ ํ๋ ์ ์์ผ๋ฉฐ ์์ ๋ก์ด ํ์ด์ง ์ด๋์ด ์ด๋ ต๋ค๋ ๋จ์ ์ด ์๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ ๋ชฉ์ ์ ๋ฐ๋ผ Pagination ๋ฐฉ์์ ์ ํํ๋ ๊ฒ์ด ์ค์ํ๋ค.
