2023. 2. 21. 11:54ใBackend/๐ฟ Spring
๋ชฉ์ฐจ
- ๊ธฐ์กด : QueryDSL์ ํ์ด์ง
- ๊ฐ์ : PageableExecutionUtils : new PageImpl()์ count ์ฟผ๋ฆฌ ๊ฐ์
- Test Case
- Test case 1. ํ์ด์ง ์ฌ์ด์ฆ 20 / ์ด content 8๊ฐ / ์ฒซ ๋ฒ์งธ ํ์ด์ง ํธ์ถ
- Test case 2. ํ์ด์ง ์ฌ์ด์ฆ 5 / ์ด content 8๊ฐ / ๋๋ฒ์งธ ํ์ด์ง ํธ์ถ
- Test case 3. ํ์ด์ง ์ฌ์ด์ฆ 3 / ์ด content 8๊ฐ / ๋๋ฒ์งธ ํ์ด์ง ํธ์ถ (์ด 3๊ฐ์ ํ์ด์ง ์กด์ฌ)
- ๊ฒฐ๋ก
(๊ธฐ์กด) QueryDSL์ ํ์ด์ง
๋ณดํต QueryDsl (์ดํ qdsl)์์ ํ์ด์ง ํ ๋ new PageImpl()์ ์ฌ์ฉํ์ต๋๋ค.
์ด๋ฆ๋๋ก Page ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ด๋ฉฐ, ๋์์ Chunk ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ ๋๋ค. (์ฌ๊ธฐ์ ์ค์ํ ๊ฒ ์๋๋ ํจ์ค)
- content : ํ์ด์ง์ ์ ์ฉํ(e.g. offset, limit) ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ
- total : ํ์ด์ง์ ์ ์ฉํ์ง ์์ ์ ์ฒด ๊ฒฐ๊ณผ์ ํฌ๊ธฐ
content ์ธ์
content ์ธ์๋ JpaQuery์ fetch() ํน์ fetchResults() ๊ฒฐ๊ณผ ๊ฐ์ ์๋ฏธํฉ๋๋ค. fetchResults()๋ Deprecated ๋์ผ๋ fetch()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์๋ ์ผ์ชฝ์ content ์ธ์์ ์์์ ๋๋ค.
.offset() .limit() ์ ์ฌ์ฉํ์ฌ ํ์ด์ง ์ ์ฉํ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
total ์ธ์
์ค๋ฅธ์ชฝ์ total ์ธ์์ ๋ค์ด๊ฐ long ํ์ ์ count์ ๋๋ค.
์์ content ์ฟผ๋ฆฌ์ ๋์ผํ where ์ ์ ์ ์ฉํ์ง๋ง, offset, limit์ ์ ์ฉํ์ง ์์ ๊ฒฐ๊ณผ์ ์๋ฅผ ์ถ๋ ฅํฉ๋๋ค. ์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ์ํด ๋ถํ์ํ join clause๋ ์ ๊ฑฐํ์ต๋๋ค.
new PageImpl() ์ ๋ฆฌ
new PageImpl()์ ์ด ๋ ๋ฒ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ํ์ด์ง์ ์ ์ฉํ๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
(๊ฐ์ ) PageableExecutionUtils : new PageImpl()์ count ์ฟผ๋ฆฌ ๊ฐ์
PageableExecutionUtils๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ์กด PageImp์ ์ฌ์ฉํ ๋ ๋ณด๋ค ์ฑ๋ฅ ์ต์ ํ๋ฅผ ํ ์ ์์ต๋๋ค. PageableExecutionUtils๋ ์ด๋ค ํน์ง์ ๊ฐ์ง๋์ง, ์ด๋ค ์์ผ๋ก ์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ํ ์ ์๋์ง ์์๋ณด๊ฒ ์ต๋๋ค.
๊ณต์๋ฌธ์์์ PageableExecutionUtils ์ค๋ช ์ ๋๋ค.
ํ์ด๋ผ์ดํธ ๋ฐ์ค์ ์์ญํ๋ฉด "๋ฐ์ดํฐ ์ฟผ๋ฆฌ๊ฐ count ์ฟผ๋ฆฌ๋ณด๋ค ๋น์ฉ์ด ๋ ๋ ๋ค๊ณ ๊ฐ์ "์ด๋ผ ํ๋ค์.
๋ป์ด ๋ช ํํ์ง ์์๋ฐ, ์ฐ๋ฆฌ์ ์ต๊ณ ์ฑ๋ฅ ๋ฒ์ญ๊ธฐ(์๋)์๊ฒ ๋ฌผ์ด๋ณด๊ฒ ์ต๋๋ค.
์ ๋ฆฌํ์๋ฉด, PageableExecutionUtils์ ์ฌ์ฉํ๋ฉด ๋จ์ new PageImpl()์ ์ฌ์ฉํ์ ๋ ๋ณด๋ค ์ฑ๋ฅ ์ต์ ํ ์ด์ ์ด ์์ต๋๋ค.
PageableExecutionUtils ํด๋์ค๋ ๋ด๋ถ์ getPage()๋ผ๋ ๋จ ํ๋์ (์ ์ ) ๋ฉ์๋๋ฅผ ๊ฐ์ง๋๋ค.
PageableExecutionUtils.getPage()
public static <T> Page<T> getPage(List<T> content, Pageable pageable, LongSupplier totalSupplier) {
Assert.notNull(content, "Content must not be null");
Assert.notNull(pageable, "Pageable must not be null");
Assert.notNull(totalSupplier, "TotalSupplier must not be null");
if (pageable.isUnpaged() || pageable.getOffset() == 0) {
if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
return new PageImpl<>(content, pageable, content.size());
}
return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}
if (content.size() != 0 && pageable.getPageSize() > content.size()) {
return new PageImpl<>(content, pageable, pageable.getOffset() + content.size());
}
return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}
}
์๋ ํ๊ธฐ๋ฅผ ๋ณด๋ฉด ๋ ์ฝ๊ฒ ์ดํดํ ์ง๋? ์๋์ง๋?
์ ํ๊ธฐ๋ฅผ ์ ๋ฆฌํ์๋ฉด
1. ์ฒซ ๋ฒ์งธ ํ์ด์ง์ด๋ฉด์ content ํฌ๊ธฐ๊ฐ ํ ํ์ด์ง์ ์ฌ์ด์ฆ๋ณด๋ค ์์ ๋ (ex, content:3๊ฐ, page ํฌ๊ธฐ: 10)
2. ๋ง์ง๋ง ํ์ด์ง์ผ ๋ (getOffset์ด 0์ด ์๋๋ฉด์, content ํฌ๊ธฐ๊ฐ ํ ํ์ด์ง์ ์ฌ์ด์ฆ๋ณด๋ค ์์ ๋)
totalSupplier ๋์ content์ size์ offset ๊ฐ์ผ๋ก total ๊ฐ์ ๋์ ํฉ๋๋ค.
PageableExecutionUtils.getPage() ๋ด๋ถ์์ ๊ฒฐ๊ตญ new PageImpl()์ ํธ์ถํ๊ณ ์์ต๋๋ค. ์ฆ new PageImpl()์ ํ๋ฒ ๋ ์ถ์ํํ๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
๊ธฐ์กด new PageImpl()์ getPage()์ ์ฐจ์ด๋ ์ธ ๋ฒ์งธ ์ธ์๋ง LongSupplier totalSupplier ๋ก ๋ณ๊ฒฝ๋๋ค๋ ๊ฒ์ด์ฃ .
long total ์ธ primitive ์ธ์๋ฅผ LongSupplier totalSupplier ํจ์ํ ์ธํฐํ์ด์ค๊ฐ ๋์ ํฉ๋๋ค.
์์ํ ํ์ ๋์ ํจ์ํ ์ธํฐํ์ด์ค๋ฅผ ๋ฐ์ ๋ ์๊ธฐ๋ ์ด์ ์ ๋ฌด์์ผ๊น์?
๊ฒฐ๊ณผ๋ก ์ ์ธ ์๊ธฐ์ง๋ง ํ์ฌ ์ํฉ์์ , ํธ์ถ ์์ ์ ์ง์ฐ์ํฌ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ด๋ค ํจ์์ ํธ์ถ ์์ ์ด์? count ์ฟผ๋ฆฌ์ ํธ์ถ ์์ ์ ๋๋ค. ๊ธฐ์กด pageImpl()์์ count ๊ฐ์ ์ธ ๋ฒ์งธ ์ธ์๋ก ๋ฃ๊ธฐ ์ํด count ์ฟผ๋ฆฌ๋ฅผ ํ์๋ก ํธ์ถํ์ง๋ง PageableExecutionUtils.getPage() ์์ count ์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ ์ธ ํจ์ํ ์ธํฐํ์ด์ค๋ฅผ ์ธ์๋ก ๋ฐ๊ณ ์์ต๋๋ค.
๋ ธ๋์ ํ์ด๋ผ์ดํธ๋ฅผ ๋ณด๋ฉด, new PageImpl()์ ์ธ๋ฒ์งธ ์ธ์๋ก totalSupplier ๋์ content.size() , pageable.getOffset() ๊ฐ์ ๋ฐ์ต๋๋ค. ์ฆ count ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ง ์์ ๋ถํ์ํ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ์ง ์์ต๋๋ค. ๊ธฐ์กด pageImpl์์ ๋ ๋ฒ์ ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋ ๊ฒ์ ๋นํด ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ด๋ฃฐ ์ ์์ต๋๋ค.
์ํ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค.
@RequiredArgsConstructor
public class BookmarkRepositoryImpl implements BookmarkRepositoryCustom {
private final JPAQueryFactory query;
private final QFolder qFolder = QFolder.folder;
private final QBookmark qBookmark = QBookmark.bookmark;
@Override
public Page<BookmarkResponse> findBookmarksWithFolderId(Long userId, Pageable pageable) {
List<BookmarkResponse> fetch = query.select(new QBookmarkResponse(qBookmark, qFolder.id))
.from(qBookmark)
.where(condition(userId))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> count = query.select(qBookmark.count())
.from(qBookmark)
.where(condition(userId));
return PageableExecutionUtils.getPage(fetch, pageable, count::fetchOne); // ์ฌ๊ธฐ
}
}
PageableExecutionUtils.getPage()์ ์ธ ๋ฒ์งธ ์ธ์์์, () -> count.fetchOne() ๋๋ค์์ ์ธ์๋ก ๋ฐ์ต๋๋ค.
์ฆ getPage() ๋ฉ์๋ ๋ด๋ถ์์ ํด๋น ๋๋ค์์ ํธ์ถํ๊ธฐ ์ ๊น์ง count ์ฟผ๋ฆฌ๋ ์คํ๋์ง ์์ต๋๋ค.
์์์ totalSupplier ํจ์ํ ์ธํฐํ์ด์ค๊ฐ ํธ์ถ๋์ง ์๋ ์กฐ๊ฑด์ ๋ค์ ๋ณด๊ฒ ์ต๋๋ค.
1. ์ฒซ ๋ฒ์งธ ํ์ด์ง์ด๋ฉด์ content ํฌ๊ธฐ๊ฐ ํ ํ์ด์ง์ ์ฌ์ด์ฆ๋ณด๋ค ์์ ๋ (ex, content:3๊ฐ, page ํฌ๊ธฐ: 10)
2. ๋ง์ง๋ง ํ์ด์ง์ผ ๋ (getOffset์ด 0์ด ์๋๋ฉด์, content ํฌ๊ธฐ๊ฐ ํ ํ์ด์ง์ ์ฌ์ด์ฆ๋ณด๋ค ์์ ๋)
test case ๋ง๋ค API๋ฅผ ํธ์ถํด ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค.
Test Case
Test case 1. ํ์ด์ง ์ฌ์ด์ฆ 20 / ์ด content 8๊ฐ / ์ฒซ ๋ฒ์งธ ํ์ด์ง ํธ์ถ
API ํธ์ถ
Debugger
์นผ๊ฐ์ด ์กฐ๊ฑด๋ฌธ์ ๊ฑธ๋ฆฌ๋ฉด์ content.size()๋ฅผ ์ธ ๋ฒ์งธ ์ธ์๋ก ๋ฐ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๊ฒฐ๊ณผ
{
"content": [
{
"id": 1,
"url": "www.google.com",
"name": "๋ฐ๋ ๋ก๋ฝ ใ
",
"folderId": 3
},
...
{
"id": 17,
"url": "chat.openai.com",
"name": "์งํผํฐ์ผ ๊ธ์ข ์จ๋ค์ค",
"folderId": 1
},
{
"id": 19,
"url": "www.google.com",
"name": "๋ฐฑ์๋",
"folderId": 1
}
],
"pageable": {
"sort": {
"empty": true,
"unsorted": true,
"sorted": false
},
"offset": 0,
"pageNumber": 0,
"pageSize": 20,
"paged": true,
"unpaged": false
},
"last": true,
"totalPages": 1,
"totalElements": 8,
"first": true,
"size": 20,
"number": 0,
"sort": {
"empty": true,
"unsorted": true,
"sorted": false
},
"numberOfElements": 8,
"empty": false
}
์คํ๋ ์ฟผ๋ฆฌ
์กฐํ ์ฟผ๋ฆฌ๋ง ์คํ๋๊ณ , count ์ฟผ๋ฆฌ๋ ์คํ๋์ง ์๋ ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค.
Test case 2. ํ์ด์ง ์ฌ์ด์ฆ 5 / ์ด content 8๊ฐ / ๋ ๋ฒ์งธ ํ์ด์ง ํธ์ถ
API ํธ์ถ
Debugger
์กฐ๊ฑด๋ฌธ์ ๊ฑธ๋ฆฌ๋ฉด์ offset ํฌ๊ธฐ์ content ์ฌ์ด์ฆ๋ฅผ ๋ํ์ฌ totalCount๋ฅผ ๋์ ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์คํ๋ ์ฟผ๋ฆฌ
Test case1๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์กฐํ ์ฟผ๋ฆฌ๋ง ์คํ๋๊ณ , count ์ฟผ๋ฆฌ๋ ์คํ๋์ง ์์ต๋๋ค.
Test case 3. ํ์ด์ง ์ฌ์ด์ฆ 3 / ์ด content 8๊ฐ / ๋ ๋ฒ์งธ ํ์ด์ง ํธ์ถ (์ด 3๊ฐ์ ํ์ด์ง ์กด์ฌ)
๋งจ ์/๋ค ํ์ด์ง๋ฅผ ์กฐํํ case 1, 2์ ๋ฌ๋ฆฌ Test Case3๋ ์ค๊ฐ ํ์ด์ง๋ฅผ ์กฐํํฉ๋๋ค.
API ํธ์ถ
Debugger
์ด์ ํ ์คํธ ์ผ์ด์ค์ ๋ฌ๋ฆฌ totalSupplier.getAsLong()์ ํธ์ถํฉ๋๋ค.
์ด์ ์๋ count ์ฟผ๋ฆฌ๊ฐ ์คํ๋์ง ์์ ๊ฒ๊ณผ ๋ฌ๋ฆฌ, ์ด๋ฒ ํ ์คํธ์ผ์ด์ค์์๋ count ์ฟผ๋ฆฌ๊ฐ ์คํ๋ ๊ฒ์ ์์ํ ์ ์์ต๋๋ค.
์คํ๋ ์ฟผ๋ฆฌ
์ง์
์กฐํ ์ฟผ๋ฆฌ์ count ์ฟผ๋ฆฌ ๋ชจ๋ ์คํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
Testcase 4. PageableExecutionUtils.getPage() ๋์ new PageImpl() ๋ฐ๋ก ์ฌ์ฉ
pageImpl์ ๋ฐ๋ก ํธ์ถํ ๋ ์ด๋จ๊น์?
์ฒซ ํ์ด์ง์ ๋ง์ง๋ง ํ์ด์ง๋ฅผ ์กฐํํ์์๋ ๋ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ชจ๋ ์คํ๋ฉ๋๋ค.
์ฆ PageableExecutionUtils์ ์ฌ์ฉํด ์ค๊ฐ ํ์ด์ง๋ฅผ ์กฐํํ ๊ฒ๊ณผ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค.
๊ฒฐ๋ก
์ด๋ก์จ PageableExecutionUtils.getPage()๋ฅผ ์ฌ์ฉํ๋ฉด ๋ถํ์ํ count ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ์ผ์ด์ค๋ฅผ ์ค์ผ ์ ์์์ ์ ์ ์์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ํ์ด์ง๋ฅผ ์กฐํํ๊ฑฐ๋ ๋ง์ง๋ง ํ์ด์ง๋ฅผ ์กฐํํ ๋๋งํผ์ ๋ถํ์ํ count ์ฟผ๋ฆฌ๊ฐ ์คํ๋์ง ์๊ฒ ๋ค์.
์ฌ์ฉ์๊ฐ ์ฒซ ํ์ด์ง์์ ๋จธ๋ฌด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ ์๋น์ค๋ผ๋ฉด ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ ์ด๋ฃจ์ด์ง ๊ฒ์ด๋ผ ์์ํด ๋ด ๋๋ค.
์ถ๊ฐ๋ก ์ ๊ฐ ๋ง๋ค๊ณ ์ ํ๋ ์๋น์ค๋ ํ์ด์ง์ ๋ชฉ์ฐจํ์ผ๋ก ๊ตฌํํ์ง ์๊ณ ์คํฌ๋กค ํ์์ผ๋ก ๊ตฌํํ๊ณ ์์ต๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์๋ noOffset์ ์ฌ์ฉํ์ฌ ์ถ๊ฐ ์ฑ๋ฅ๊ฐ์ ์ ํ ์ ์๋๋ฐ ๋ค์ ํฌ์คํธ์์ ๋ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ์๋ฝ!