11/12) 게시판 검색, pagination, 세션 타임 아웃

AI·2025년 11월 12일

게시판 검색

제목을 기준으로 포함된 내용을 검색을 하고자 하였다.

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>

pagination

개념


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

pagination을 선택하는 중요한 요소

  • 한페이지당 몇 개씩 나오게 할 것인지?
    247개의 게시글이 있다고 가정 -> 5개씩 * 49페이지+2 or 10개씩 * 24페이지+7
  • 페이지 링크의 개수는?
    3, 5

전체 건수

  • service는 수정 x
    controller -> service -> dao 2번 보다 controller -> service 1번, service -> dao 2번으로 더 효율적으로 TotalCount 가져옴
    service에서 BoardResultDto객체의 count까지 마무리하고 return

효율성과 유지보수성을 항상 생각을 하며 만들어야 함

// 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">&laquo;</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">&raquo;</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 활용으로 좀 더 쉽게 만들 수 있음

최종본

0개의 댓글