2021. 1. 8. 15:00ใ๐ฑ Develop/Server
โ Pagination (ํ์ด์ง๋ค์ด์ )
๋ฐฑ์๋์์ ํด๋ผ์ด์ธํธ์๊ฒ ๊ฐ์ ์ ๋ฌํ ๋, ์ผ์ ๊ธฐ์ค์ผ๋ก ๋ถํ ํ์ฌ ์ ๋ฌํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์น์์ ๊ฒ์๊ธ์ 1ํ์ด์ง, 2ํ์ด์ง๋ก ๋๊ธฐ๋ ๊ฒ๊ณผ '๋๋ณด๊ธฐ' ๋ฒํผ์ผ๋ก ๋ฌดํ ์คํฌ๋กค์ ํ ์ ์๋ ๊ฒ ๋ชจ๋ ํ์ด์ง๋ค์ด์ ์ ํตํด ๊ตฌํ๋ ๊ฒ์ ๋๋ค. ํ์ด์ง๋ค์ด์ ์ ๋ฐฉ์์๋ ํฌ๊ฒ 2๊ฐ์ง๊ฐ ์๋๋ฐ, ํ๋๋ offset ๋ฐฉ์์ด๊ณ ํ๋๋ cursor ๋ฐฉ์์ ๋๋ค.
1. ์คํ์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ (offset pagination)
- limit, offset ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ๋ถํจ.
- ์ฃผ๋ก ์น ํ์ด์ง์์ << 1 | 2 | 3 >> ์ด๋ฐ ๋ฐฉ์์ ๋ค๋น๊ฒ์ด์ ์ ์ธ ๋ ์ฌ์ฉ
2. ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ (cursor pagination)
- ๊ฐ์ฅ ์ต๊ทผ idx๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ตฌ๋ถํจ.
- ์ฃผ๋ก ๋ฌดํ ์คํฌ๋กค์ด๋ ๋๋ณด๊ธฐ๋ฅผ ๊ตฌํํ ๋ ์ฌ์ฉ(SNS)
์ด๋ฒ ํฌ์คํ ์์๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ํ์ด์ง ๋ฐฉ์์ธ offset๋ถํฐ ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.
1๏ธโฃ Offset Pagination์ผ๋ก ํ์ด์งํ๊ธฐ
offset pagination์ ์ ์ฒด ๋ฐ์ดํฐ ๊ฐ์์ "offset๋ถํฐ limit๊น์ง" ๋ง๊ฐ์ ธ์ค๋ ๋ฐฉ์์ ๋๋ค. ์๋์ ๊ฐ์ select๋ฌธ์ ์ผ์ ๋ aritst ํ ์ด๋ธ์์๋ artist_idx๊ฐ 1๋ถํฐ 5๊น์ง์ ๊ฐ์ ๋ฐํํด์ค๋๋ค.
select * from articles LIMIT 0,5
์ด์ ์ผ์ ํ๊ฒ ํ์ด์ง๋ก ๋ฐ์๋ณด๋ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค. ๋จผ์ , ํด๋ผ์ด์ธํธ์์๋ ๋ค์๊ณผ ๊ฐ์ url๋ก ์ผ์ ํ๊ฒ ์์ฒญ์ด ๋ค์ด์จ๋ค๊ณ ๊ฐ์ ํด๋ณด๊ฒ ์ต๋๋ค.
/artist/list?page=1&pageSize=5
page๋ ์ด๋์๋ถํฐ ๊ฐ์ ธ์ฌ์ง ์์ํ offset์ ๊ณ์ฐํ ๊ฐ์ด๊ณ , pageSize๋ offset์์๋ถํฐ ๋ช ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ์ง๋ฅผ ์ ํํ๋ ์์ ๋๋ค. ์ฆ, select ๋ฌธ์์ "LIMIT start,end"์์ ๊ฐ๊ฐ start์ end์ ๋ค์ด๊ฐ ์ซ์๋ผ๊ณ ์๊ฐํ์๋ฉด ์ฝ์ต๋๋ค.
๐ Controller/artist.js
๋จผ์ Controller์์๋ req.query๋ก page์ pageSize๋ฅผ ๋ฐ์์ต๋๋ค. ๊ฐ ์ฒ๋ฆฌ๋ service๋ก ๋๊ฒจ์ ์ฒ๋ฆฌํ๊ฒ ์ต๋๋ค.
exports.artist = async (req, res) => {
const pageInfo = req.query;
const page = parseInt(pageInfo.page);
const pageSize = parseInt(pageInfo.pageSize);
const artistName = pageInfo.name;
try {
if (!pageInfo || !currentId || !pageSize) {
res.status(sc.BAD_REQUEST).send(au.successFalse(rm.NULL_VALUE));
}
**const artist_list = await userService.cursorArtist(page, pageSize, artistName);**
artist_list == false ? res.status(sc.BAD_REQUEST).send(au.successFalse(rm.DB_NOT_MATCHED_ERROR)) : res.status(sc.OK).send(au.successTrue(rm.DB_SUCCESS, artist_list));
} catch (err) {
res.status(sc.INTERNAL_SERVER_ERROR).send(au.successFalse(rm.INTERNAL_SERVER_ERROR));
throw err;
}
};
๐ Service/artist.js
๋จผ์ page๋ "์ด๋์๋ถํฐ ์์ํ ์ง"๋ฅผ ๋ํ๋ผ ์ ์๋ ์๋ผ๊ณ ์๊ฐํด๋ด ์๋ค. ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ๊ณ ์ค๊ธฐ ์ํด์๋ ์ด์ ํ์ด์ง๊ฐ ๋๋ ์์น๋ฅผ ์์์ผํฉ๋๋ค. ๊ทธ๊ฑธ ๊ณ์ฐํ๋ ๊ฒ์ด ํ์ด์ง์ ๊ด๊ฑด์ ๋๋ค. ์ฌ๊ธฐ์ ๊ฐ๊ณผํ๊ธฐ ์ฌ์ด ๋ถ๋ถ์ DB์์ LIMIT์ผ๋ก ๊ฐ์ ๋ถ๋ฌ์ฌ ๋๋ 0๋ถํฐ ์์ํฉ๋๋ค. ์ฆ, page =1์ด ๋ค์ด์ค๋ฉด ๋ฐฑ์๋์์๋ 0๋ถํฐ ์์์ผ๋ก ์๊ฐํด์ผํ๋ค๋ ๊ฑฐ์ฃ .
exports.getArtist = async (page, pageSize) => {
try {
let start = 0;
if (page <= 0) {
page = 1;
} else {
start = (page - 1) * pageSize;
}
const cnt = await user.Cnt(data);
if (page > Math.round(cnt[0].total / pageSize)) {
return null;
}
const artist_list = await user.pageAll(start, pageSize);
return artist_list;
} catch (err) {
console.log('์ํฐ์คํธ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ ์คํจ.', err);
throw err;
}
};
page =1 , pageSize =5 ์ผ ๋ LIMIT 0,5
page =2, pageSize =5 ์ผ ๋ LIMIT 5,5
page =3, pageSIze = 5 ์ผ ๋ LIMIT 10,5 ....์ด๋ฐ ์์ผ๋ก ๊ฐ์ด ๋ค์ด๊ฐ์ผํฉ๋๋ค.
let start = 0;
if (page <= 0) { // 0 ์ดํ์ ํ์ด์ง๋ฅผ ์์ฒญํ๋ฉด
page = 1; // 1ํ์ด์ง๋ก ๊ฐ๋๋ก ํ๋ค.
} else {
start = (page - 1) * pageSize;
}
๊ฒฐ๊ตญ ๋ค์ ํ์ด์ง์ ์์์ ์ (page-1) * pageSize๋ผ๋ ๊ฒฐ๋ก ์ด ๋์ต๋๋ค. pageSize๋ ์ผ๋ง๋งํผ ๊ฐ์ ธ์ฌ์ง์ ๋ํ ๋ฌธ์ ์ด๋ฏ๋ก ๊ทธ๋๋ก ๋ฃ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค. ํ์ด์ง์ ๊ณ์ฐํ๋ ๋ฐฉ๋ฒ์ ๋ค์ํ๋ฐ, ์ด๋ฒ ๊ฒฝ์ฐ๋ pageSize๋ ๊ฐ์ด ๋ณ์๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ pageSize๋ ๋ฐ๋ก ์ฒ๋ฆฌํ์ง ์๊ณ ๋ฐ๋ก sql๋ฌธ์ ๋ฃ์ด์ฃผ์์ต๋๋ค.
โ๏ธ์ด์ ๊ฑฐ์ ๋ค์์ต๋๋ค.
ํ์ด์ง๋ฅผ ์์ฒญํ๋ค๋ณด๋ฉด, ํด๋ผ์ด์ธํธ๋ ๋ฐ์ดํฐ๊ฐ ๋ช ๊ฐ๋ ์๋์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๋ณด๋ค ๋ง์ ์ซ์์ ํ์ด์ง๋ฅผ ํธ์ถํ ์ ๋ ์์ต๋๋ค. artist ํ ์ด๋ธ๋ก ์๋ฅผ ๋ค์๋ฉด, ๋ฐ์ดํฐ๋ 20๊ฐ๊ณ 1ํ์ด์ง ๋น 5๊ฐ์ฉ ์์ฒญํ๋ค๋ฉด ์ด 4ํ์ด์ง๊ฐ ์ ๋ถ์ผ ๊ฒ์ ๋๋ค.
ํ์ง๋ง ์ด๋ฅผ ๋ชจ๋ฅด๋ ํด๋ผ์ด์ธํธ๊ฐ 5ํ์ด์ง๋ฅผ ์์ฒญํ๋ค๋ฉด ์๋ฌ๊ฐ ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์์ต๋๋ค.
const cnt = await user.Cnt(data); // selectํ ์ ์ฒด ๊ฒฐ๊ณผ ์
if (page > Math.round(cnt[0].total / pageSize)) {
return null;
}
ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ page์ ๊ฐ์ด select ํ ๊ฒฐ๊ณผ ๊ฐ์ ์ ์ฒด ์๋ฅผ pageSize๋ก ๋๋์ด ์ฌ๋ฆผํ ๊ฐ๋ณด๋ค ํฐ ๊ฒฝ์ฐ๋ null์ ๋ฐํํ์ฌ ํด๋ผ์ด์ธํธ๊ฐ ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ค๋๋ค.
๐ Service/artist.js
์ด์ Service์์ ๊ณ์ฐํ start์ end ๊ฐ์ model์์ ๋ฐ์์ sql๋ฌธ์ ์คํํ๊ณ ๊ฐ์ ๋ฐํํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
exports.pageAll = async (start, end) => {
try {
const sql = `SELECT * FROM ARTISTS_TB LIMIT ${start}, ${end}`;
const result = await pool.queryParam(sql);
if (result.length === 0) {
return false;
} else {
return result;
}
} catch (err) {
throw err;
}
};
๐โ๏ธ ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธํด๋ณด๊ธฐ
GET์ผ๋ก ์ค์ ํด์ฃผ๊ณ localhost:3000/artist?page=1&pageSize=5๋ฅผ ํด์ค๋๋ค. ํ์ฌ ๋ค๋ฅธ ์ฝ๋๋ก ๊ฐ๋ฐ ์ค์ด์ด์ ์ฌ์ง ์ url์ด ๋ค๋ฅด์ง๋ง ๊ฒฐ๊ณผ๋ ๊ฐ์ผ๋ ์ url๋ก ์งํํด์ฃผ์ธ์.
์ง๊ธ๊น์ง node.js์์ offset ๊ธฐ๋ฐ ํ์ด์ง์ ๊ตฌํํด๋ณด์์ต๋๋ค.
๐ค Offset ๋ฐฉ์์ ๋ฌธ์
offset ๋ฐฉ์์ ์๋ฅผ ๋ค์ด LIMIT 4000, 100์ด๋ผ๊ณ ํ๋ค๋ฉด offset ๋ฐฉ์์ 4000๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ฝ์ ๋ค์ 100๊ฐ๋ฅผ ์ ํํด์ ๊ฐ์ ธ์ต๋๋ค. ๊ฒฐ๊ณผ๋ฅผ ๋ค ๊ฐ์ง๊ณ ์์ ๊ทธ ์์์ limit์ ๊ฑธ์ด์ฃผ๊ธฐ ๋๋ฌธ์ ์ ์ฒด ๊ฒ์๊ธ ์๋ฅผ ์๊ธฐ์๋ ํธํฉ๋๋ค.
ํ์ง๋ง select ํด์จ ๊ฒฐ๊ณผ ๊ฐ์ด ๋ง์์ง๋ฉด ๋ง์์ง ์๋ก ์ํํ๋ rows ์๊ฐ ๋ง์์ง๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ๋ฐ์ ์์ต๋๋ค. ๋ํ ๋ฌดํ ์คํฌ๋กค์ด๋ '๋๋ณด๊ธฐ' ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ cursor ๊ธฐ๋ฐ์ ํ์ด์ง์ผ๋ก ๋์ด๊ฐ๋ ํธ์ด ํจ์ฌ ์ข์ต๋๋ค.
๋ค์ ํฌ์คํ ์์๋ ๊ทธ๋์ cursor ๋ฐฉ์์ผ๋ก ๋์ด๊ฐ๋ ๊ฒ์ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค!
๐ ์ฐธ๊ณ