제목을 기준으로 포함된 내용을 검색을 하고자 하였다.
mapper
-- test
select b.board_id, b.user_seq, u.user_name, u.user_profile_image, b.title, b.reg_dt, b.read_count
from board b, users u
where b.user_seq = u.user_seq
and b.title like '%1%'
order by b.board_id desc
limit 5 offset 0;
=====
-- mapper
<select id="listBoardSearchWord" parameterType = "com.mycom.myapp.board.dto.BoardParamDto" resultType="com.mycom.myapp.board.dto.BoardDto">
select b.board_id, b.user_seq, u.user_name, u.user_profile_image, b.title, b.reg_dt, b.read_count
from board b, users u
where b.user_seq = u.user_seq
and b.title like concat('%', #{searchWord}, '%')
order by b.board_id desc -- 최신 글부터 조회를 위해
limit #{limit} offset #{offset};
</select>
like 검색, order by, limit&offset으로 검색 결과 테스트
dao, service도 수정
// dao
// listBoard 파라미터 추가
List<BoardDto> listBoard(BoardParamDto boardParamDto);
// search 메소드 새롭게 추가
List<BoardDto> listBoardSearchWord(BoardParamDto boardParamDto);
====
// service
BoardResultDto listBoard(BoardParamDto boardParamDto);
BoardResultDto listBoardSearchWord(BoardParamDto boardParamDto);
====
// serviceImpl
@Override
public BoardResultDto listBoardSearchWord(BoardParamDto boardParamDto) {
BoardResultDto boardResultDto = new BoardResultDto();
try {
List<BoardDto> list = boardDao.listBoardSearchWord(boardParamDto);
boardResultDto.setList(list);
boardResultDto.setResult("success");
}catch(Exception e) {
e.printStackTrace();
boardResultDto.setResult("fail");
}
return boardResultDto;
}
controller
// 파라미터 존재 유무로 처리(검색, 목록) -> 프론트 편리
@GetMapping("/list")
public BoardResultDto listBoard(BoardParamDto boardParamDto) {
BoardResultDto boardResultDto = null;
if( Strings.isEmpty(boardParamDto.getSearchWord()) ) { // isEmpty: null, "" / isBlank: null, "", " ", "/t"...
boardResultDto = boardService.listBoard(boardParamDto);
} else {
boardResultDto = boardService.listBoardSearchWord(boardParamDto);
}
return boardResultDto;
}
강사님은 위와 같은 방식으로 작성하였지만, 개인적으로는 아래와 같은 방식을 처음에 생각을 하였다. 아래와 같이 동작을 시켜도 잘 작동한다.
import org.springframework.util.StringUtils;
@GetMapping("/list")
public BoardResultDto listBoard(BoardParamDto boardParamDto) {
// 2. hasText()로 null, "", 공백을 모두 체크
if (StringUtils.hasText(boardParamDto.getSearchWord())) {
return boardService.listBoardSearchWord(boardParamDto);
}
return boardService.listBoard(boardParamDto);
}

board.jsp - 검색 버튼을 누르면, 검색 입력칸에 작성한 값을 파라미터로 추가하여 작동되게 하였다.
<script>
//pagination
let LIST_ROW_COUNT = 10; // const limit이 가능하지만 let을 사용한다고 함
let OFFSET = 0; // LIST_ROW_COUNT * page number
let SEARCH_WORD = '';
window.onload = function(){
...
// search
document.querySelector("#btnSearchWord").onclick = function() {
let searchWord = document.querySelector("#inputSearchWord").value;
// if(searchWord == ''){
// alert("Please enter a search term");
// return;
// }
SEARCH_WORD = searchWord;
listBoard();
}
}
// url 수정
async function listBoard(){
let url = "/boards/list";
let urlParams = "?limit=" + LIST_ROW_COUNT + "&offset="+ OFFSET + "&searchWord=" + SEARCH_WORD;
let response = await fetch(url + urlParams);
let data = await response.json();
...
}
</script>

방식 3가지 - 번호를 누르면 특정 페이지를 보는 pagination, 계속 스크롤을 내릴 수 있는 infinite scroll, 두 개를 합쳐 버튼을 누르면 다음 스크롤을 볼 수있는 hybrid 방식 존재
pagination을 선택하는 중요한 요소


효율성과 유지보수성을 항상 생각을 하며 만들어야 함
// dao
// 전체 게시물 수
int listBoardTotalCount();
int listBoardSearchWordTotalCount(BoardParamDto boardParamDto);
// serviceImpl - listBoard, listBoardSearchWord에 count 추가
int count = boardDao.listBoardSearchWordTotalCount(boardParamDto);
boardResultDto.setCount(count);
mapper
<select id="listBoardTotalCount">
select count(*) from board;
</select>
<select id="listBoardSearchWordTotalCount" parameterType = "com.mycom.myapp.board.dto.BoardParamDto">
select count(*) from board
where title like concat('%', #{searchWord}, '%');
</select>
board.jsp
<div id="paginationWrapper"></div>
<script>
let PAGE_LINK_COUNT = 10; // 화면에 보여줄 페이지 링크 수
let CURRENT_PAGE_INDEX = 1; // 현재 페이지 링크 인덱스
let TOTAL_LIST_COUNT = 0; // 현재 게시글 전체 수
//async function listBoard()에 추가
TOTAL_LIST_COUNT = data.count;
addPagination();
// util.js로 함수 구현 예정
function addPagination(){
console.log("addPagination()", LIST_ROW_COUNT, PAGE_LINK_COUNT, CURRENT_PAGE_INDEX, TOTAL_LIST_COUNT);
}
</script>
util.js
function makePaginationHtml(listRowCount, pageLinkCount, currentPageIndex, totalListCount, htmlTargetId){
let targetUI = document.querySelector("#"+htmlTargetId);
let pageCount = Math.ceil(totalListCount/listRowCount);
let startPageIndex = 0;
if( (currentPageIndex % pageLinkCount) == 0 ){ //10, 20...맨마지막
startPageIndex = ((currentPageIndex / pageLinkCount)-1)*pageLinkCount + 1
}else{
startPageIndex = Math.floor(currentPageIndex / pageLinkCount)*pageLinkCount + 1
}
let endPageIndex = 0;
if( (currentPageIndex % pageLinkCount) == 0 ){ //10, 20...맨마지막
endPageIndex = ((currentPageIndex / pageLinkCount)-1)*pageLinkCount + pageLinkCount
}else{
endPageIndex = Math.floor(currentPageIndex / pageLinkCount)*pageLinkCount + pageLinkCount;
}
let prev;
if( currentPageIndex <= pageLinkCount ){
prev = false;
}else{
prev = true;
}
let next;
if(endPageIndex > pageCount){
endPageIndex = pageCount
next = false;
}else{
next = true;
}
let pagainationHTML = `<ul class="pagination justify-content-center">`;
if( prev ){
pagainationHTML +=
`<li class="page-item">
<a class="page-link" href="javascript:movePage(${startPageIndex - 1});" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>`;
}
for( let i = startPageIndex; i <= endPageIndex; i++ ){
if( i == currentPageIndex ){
pagainationHTML += `<li class="page-item active"><a class="page-link" href="javascript:movePage(${i});">${i}</a></li>`;
}else{
pagainationHTML += `<li class="page-item"><a class="page-link" href="javascript:movePage(${i});">${i}</a></li>`;
}
}
if( next ){
pagainationHTML +=
`<li class="page-item">
<a class="page-link" href="javascript:movePage(${endPageIndex + 1});" aria-label="Previous">
<span aria-hidden="true">»</span>
</a>
</li>`
}
pagainationHTML += `</ul>`;
targetUI.innerHTML = pagainationHTML;
}
board.jsp에 addPagination() 및 movePage(pageIndex) 구현
<script>
// 데이터 건수가 굉장히 큰 경우, 정확한 총 건수 대신 대략적인 건수 테이블 만듦
// 총 건수 테이블 생성, 충분한 시간 간격으로 한번씩 업데이트 - 최신 데이터 위주로 찾아보기에
// 그 외 다양한 방식으로 대응 방법 존재 ex. 테이블 분리...
// 테이블 분리 : 최신 데이터 테이블과 이전 데이터 테이블 분리 (이관), 이관 테이블 총건수는 미리 계산
function addPagination(){
console.log("addPagination()", LIST_ROW_COUNT, PAGE_LINK_COUNT, CURRENT_PAGE_INDEX, TOTAL_LIST_COUNT);
makePaginationHtml(LIST_ROW_COUNT, PAGE_LINK_COUNT, CURRENT_PAGE_INDEX, TOTAL_LIST_COUNT, "paginationWrapper");
}
function movePage(pageIndex){
CURRENT_PAGE_INDEX = pageIndex;
OFFSET = (pageIndex - 1) * LIST_ROW_COUNT;
listBoard();
}
</script>

세션 타임 아웃이 되면, 로그인해서 사용하던 페이지는 다시 로그인이 필요하니까 로그인 페이지로 이동시키기
loginInterceptor수정
@Component
public class LoginInterceptor implements HandlerInterceptor{
private final String jsonStr = "{\"result\":\"login\"}";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 기존 코드...
if(userDto == null) {
// 거절
// 데이터 요청
if("true".equals(request.getHeader("ajax"))) {
System.out.println("LoginInterceptor >> ajax request fail");
response.getWriter().write(jsonStr);
//response.sendRedirect("/pages/login"); // 이렇게 쓰면 안되나?
return false;
} else {
// 페이지 요청 방식
return false;
}
}
// 통과
return true;
}
}
let data = await response.json();이기에 json형태로 받아야지 바로 html로 redirect를 하지 못 함
board.jsp - login을 하고 사용해야 하는 함수 모든 곳에 아래 내용 추가
<script>
// function{
let fetchOptions = {
headers:{
ajax:"true"
}
}
// ...
let response = await fetch(url, fetchOptions);
// ...
else if(data.result == "login"){
window.location.href = "/pages/login";
}
// }
</script>
util.js에 async function를 추가하거나 axios 활용으로 좀 더 쉽게 만들 수 있음
