링크를 올려두긴 했는데 복잡해 보이고 이해도 잘 안되고 JPA에서 지원해주는 라이브러리 쓰는 거 같아서 그냥 생짜로 했다.
당연히 똑똑하고 많이 배운 사람들이 만들어준 걸 잘 갖다 쓰는게 성능이든 안정성이든 뭐든 좋을텐데 검색해서 찾는 건 걸리는 시간이 복불복이고 신뢰성도 상대적으로 떨어지고 공식문서는 영어로 된 거 보다보면 너무 힘들때가 많고... 정말 이럴 때 영어 잘해야겠다고 느낀다. 몇몇 프레임워크/라이브러리 번역해주시는 분들께 항상 감사해야...
일단 쭉 해놓고 github의 diff 부분을 보면서 정리해본다.
몇몇 공통 부분 수정은 큰 의미 없는 것 같고(api/board.js->api/post.js 라든가 post에서 쓰는 axios api 분리해서 쓰는 거라든가) 결국 중요한 건 페이징 혹은 페이지네이션 부분 같은데(pagination이 더 맞는 어휘 같다),
현재 페이지, 페이징 사이즈(아래 나타나는 갯수), 리스트 사이즈(화면에 나타나는 게시물 갯수)를 지정해두고 관련 변수들의 크기에 따라 처음으로 불러올 게시물/마지막으로 불러올 게시물을 정해 호출했다.
function fetchBoard(paging) {
const { currentPage, pagingSize, listSize } = paging;
const firstPostIndex = (currentPage-1) * pagingSize + 1;
const lastPostIndex = firstPostIndex+listSize-1;
return postAxios.get(`?firstPostIndex=${firstPostIndex}&lastPostIndex=${lastPostIndex}`);
}
좀 더 깔끔하게 쓰는 방법도 있을 것 같은데 Axios를 더 공부해야 할까? 어쨌든 잘 날아간다.
@GetMapping(path="/post")
public List<BoardDto> getBoardList(@RequestParam int firstPostIndex, @RequestParam int lastPostIndex)
throws Exception{
return boardService.getBoardList(firstPostIndex, lastPostIndex);
}
적당히 @RequestParam을 쓴 레스트컨트롤러 안의 메서드로 받아서 넘긴다.
서비스->서비스구현체->매퍼->mybatis xml sql문으로 넘어가면,
<select id="selectBoardList" parameterType="int" resultType="site.bfor0312.bfor0312_BE.board.dto.BoardDto">
<![CDATA[
SELECT
*
FROM
(
SELECT
row_number() over(order by board_idx desc)
as ROWNUM,
board_idx,
title,
creator_id,
hit_cnt,
created_datetime
FROM t_board
WHERE deleted_yn = 'N'
) t_table_filtered
WHERE
ROWNUM BETWEEN #{firstPostIndex} and #{lastPostIndex}
]]>
</select>
대강 이런 식으로 받아온다. 대충 구현은 했는데 성능 등에서 문제가 없는 쿼리문인지는 잘 모르겠다. board_idx는 pk라 인덱싱은 되어있을 테니 괜찮을 것 같긴 한데...
그리고 오라클과는 다르게 MySQL은 인라인 뷰 테이블에 Alias가 없으면 에러가 떠서 넣어줬다.
특정 파라미터를 받는 것도 param1 param2 이런 식으로도 받을 수 있는 거 같은데 그래도 뭐 코드 보고 바로 이해가 되는게 편하니까.
그리고 페이지의 총 갯수가 있어야 하기 때문에 한 번 더 호출을 해서 t_board에서 삭제되지 않은 게시물의 row 수를 받아오게 해놨다. 이 부분이 조금 불만인데 사용자 입장에서 한 번의 입력을 했는데 백엔드에 두 번 호출한다는게 불편한데 어떻게 바꾸면 좋을지 잘 모르겠다.
게시물 목록 보여주는 뷰 인스턴스 PostList.vue
<template>
<div class="board-list">
<table class="w3-table-all" summary="연습용 자유 게시판">
<thead>
<tr>
<th v-for="header, idx in tableHeader" :key="idx" scope="col" v-bind:id="header" > {{header}} </th>
</tr>
</thead>
<!-- <tfoot>
</tfoot> -->
<tbody>
<tr v-for="(row, idx) in postList" :key="idx" scope="row">
<td v-for="(key, idx) in row" :key="idx">
<div v-if="key==row.title">
<router-link :to="`/board/post/${row.boardIdx}`">
{{ key }}
</router-link>
</div>
<div v-else>
{{ key }}
</div>
</td>
</tr>
</tbody>
</table>
<div
class="pagination w3-bar w3-padding-16 w3-small"
>
<span class="pg">
<p class="first w3-button w3-border" @click="changePage(1)">{{'<<'}}</p>
<p
class="prev w3-button w3-border"
@click="changePage(paging.currentPage-1)"
disabled=true
>{{'<'}}</p>
<template v-for="(number, index) in pageNumbers" :key="index" >
<template v-if="paging.currentPage===number">
<strong class="w3-button w3-border w3-green">{{ number }}</strong>
</template>
<template v-else>
<p class="w3-button w3-border" @click="changePage(number)">{{ number }}</p>
</template>
</template>
<p class="next w3-button w3-border" @click="changePage(paging.currentPage+1)">{{'>'}}</p>
<p class="last w3-button w3-border" @click="changePage(paging.lastPagingNumber)">{{'>>'}}</p>
</span>
</div>
</div>
</template>
<script>
import {fetchBoard, fetchBoardSize } from '@/api/post';
import _ from 'lodash';
export default {
data() {
return {
tableHeader: {},
postList: {},
paging: {
currentPage: 1,
pagingSize: 10,
listSize: 10,
lastPagingNumber: undefined,
},
pageNumbers: [],
}
},
mounted() {
this.GetList();
this.initPaging();
},
methods: {
async GetList() {
const {data:rawPostList} = await fetchBoard(this.paging);
const filteredPostList = rawPostList.map(
post => _.pickBy(post, (value) => { return !_.isNil(value) })
);
this.postList = filteredPostList;
this.initHeader(Object.keys(filteredPostList[0]));
},
changePage(number){
if(number<1) return;
if(number>this.paging.lastPagingNumber) number = this.paging.lastPagingNumber;
this.paging.currentPage=number;
this.GetList();
this.initPaging();
},
initHeader(tableHeader) {
this.tableHeader=tableHeader;
},
async initPaging(){
const {data} = await fetchBoardSize();
let boardSize = data.boardSize;
this.paging.lastPagingNumber = parseInt(boardSize/this.paging.pagingSize)+1;
let startPageNumber = parseInt(((this.paging.currentPage-1)/this.paging.pagingSize))*this.paging.pagingSize+1;
this.pageNumbers = Array(this.paging.pagingSize).fill(0).map(
(zero, index) => zero+index+startPageNumber
).filter(number => number <= this.paging.lastPagingNumber);
}
},
}
</script>
changePage가 조금 지저분한데... 입력 받는 것도 그렇고 안에서 GetList나 initPaging 호출하는 것도 좀 그렇고... watch로 currentPage 변화 감시해서 불러주는 게 나을 것 같은데 나중에 해야겠다.
-> 해놨음. immediate 줘서 mounted()도 없애버림.
watch: {
paging: {
deep: true,
immediate: true,
handler() {
this.GetList();
this.initPaging();
},
},
},
** 지금 소스를 보니 PostList 컴포넌트에 Paging이 들어가 있어서 확인해서 PostListPage.vue 쪽으로 분리 해야할듯.
좀 읽어봐야 할 링크
https://memostack.tistory.com/244
https://dev-gorany.tistory.com/16
https://devlog-wjdrbs96.tistory.com/182
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html