Spring

Spring MVC 게시판 만들기 5편: 페이징과 검색 기능 UI 구현하기


💡 이 글에서 배울 것
화면(JSP)에서 페이징과 검색 UI를 어떻게 구현하는지, 검색 조건을 유지하는 방법까지 알아봅니다.


🎯 한눈에 보기

주제핵심 내용
JSTLJSP에서 반복문, 조건문 사용
Bootstrap스타일링된 페이지네이션
JavaScript검색 조건 유지하며 페이지 이동

📌 이번 편에서 배울 것

이번 편에서는 화면(JSP)에서 페이징과 검색을 어떻게 구현하는지 알아봅니다:

  • JSTL로 목록 출력하기
  • Bootstrap 페이지네이션
  • JavaScript 이벤트 처리
  • 검색 조건 유지하기

📋 게시글 목록 화면 (list.jsp)

list.jsp는 크게 3가지 영역으로 구성됩니다:

flowchart TB
    subgraph "list.jsp 구성"
        A["📊 게시글 테이블"]
        B["🔍 검색 필터"]
        C["📄 페이지네이션"]
    end
    
    A --> B --> C

📊 게시글 테이블 (JSTL 반복문)

Controller에서 전달받은 dto.boardDTOList<c:forEach>로 반복합니다.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<table class="table table-boardered">
    <thead>
        <th>Bno</th>
        <th>Title</th>
        <th>Writer</th>
        <th>RegDate</th>
    </thead>
    <tbody>
        <c:forEach var="board" items="${dto.boardDTOList}">
            <tr data-bno="${board.bno}">
                <td><c:out value="${board.bno}" /></td>
                <td>
                    <!-- 상세보기 링크 (검색 조건 유지) -->
                    <c:url var="readUrl" value="/board/read/${board.bno}">
                        <c:param name="page" value="${dto.page}" />
                        <c:param name="size" value="${dto.size}" />
                        <c:param name="types" value="${dto.types}" />
                        <c:param name="keyword" value="${dto.keyword}" />
                    </c:url>
                    <a href="${readUrl}">
                        <c:out value="${board.title}" />
                    </a>
                </td>
                <td><c:out value="${board.writer}" /></td>
                <td><c:out value="${board.createdDate}" /></td>
            </tr>
        </c:forEach>
    </tbody>
</table>

💡 <c:out>을 쓰는 이유: XSS(크로스 사이트 스크립팅) 공격 방지! HTML 태그가 실행되지 않도록 이스케이프합니다.


🔍 검색 필터 UI

Bootstrap Select와 Input을 사용한 검색 폼:

<div class="d-flex justify-content-end">
    <!-- 검색 타입 선택 -->
    <select name="typeSelect" class="form-select me-2">
        <option value="">--</option>
        <option value="T"   ${dto.types == 'T'   ? 'selected' : ''}>제목</option>
        <option value="C"   ${dto.types == 'C'   ? 'selected' : ''}>내용</option>
        <option value="W"   ${dto.types == 'W'   ? 'selected' : ''}>작성자</option>
        <option value="TC"  ${dto.types == 'TC'  ? 'selected' : ''}>제목 OR 내용</option>
        <option value="TW"  ${dto.types == 'TW'  ? 'selected' : ''}>제목 OR 작성자</option>
        <option value="TCW" ${dto.types == 'TCW' ? 'selected' : ''}>제목 OR 내용 OR 작성자</option>
    </select>
    
    <!-- 검색어 입력 -->
    <input type="text" class="form-control me-2" 
           name="keywordInput" value="<c:out value='${dto.keyword}'/>" />
    
    <!-- 검색 버튼 -->
    <button class="btn btn-outline-info searchBtn">Search</button>
</div>

💡 ${dto.types == 'T' ? 'selected' : ''}: 현재 검색 타입이면 선택 상태 유지!


📄 페이지네이션 UI

페이지네이션 UI

Bootstrap의 pagination 컴포넌트를 사용합니다:

<ul class="pagination">
    <!-- 이전 버튼 -->
    <c:if test="${dto.prev}">
        <li class="page-item">
            <a class="page-link" href="${dto.start - 1}">Prev</a>
        </li>
    </c:if>
    
    <!-- 페이지 번호들 -->
    <c:forEach var="num" items="${dto.pageNums}">
        <li class="page-item ${dto.page == num ? 'active' : ''}">
            <a class="page-link" href="${num}">${num}</a>
        </li>
    </c:forEach>
    
    <!-- 다음 버튼 -->
    <c:if test="${dto.next}">
        <li class="page-item">
            <a class="page-link" href="${dto.end + 1}">Next</a>
        </li>
    </c:if>
</ul>

화면 모습:

[Prev] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [Next]

                   현재 페이지

⚡ JavaScript - 페이지 클릭 처리

페이지 링크를 클릭하면 검색 조건을 유지하면서 페이지를 이동합니다.

const pagingDiv = document.querySelector(".pagination");

pagingDiv.addEventListener("click", (e) => {
    e.preventDefault();     // 기본 링크 동작 막기
    e.stopPropagation();    // 이벤트 버블링 방지
    
    const target = e.target;
    const targetPage = target.getAttribute("href");  // 클릭한 페이지 번호
    const size = ${dto.size} || 10;
    
    // URL 파라미터 구성
    const params = new URLSearchParams({
        page: targetPage,
        size: size
    });
    
    // 현재 검색 조건이 있으면 추가
    const types = '${dto.types}';
    const keyword = '${dto.keyword}';
    
    if (types && types !== '' && keyword && keyword !== '') {
        params.set("types", types);
        params.set("keyword", keyword);
    }
    
    // 페이지 이동
    window.location.href = '/board/list?' + params.toString();
});
flowchart LR
    Click["3페이지 클릭"] --> Prevent["기본 동작 방지"]
    Prevent --> Build["URL 파라미터 구성"]
    Build --> Navigate["/board/list?page=3&size=10&types=T&keyword=스프링"]

🔍 JavaScript - 검색 버튼 처리

document.addEventListener("DOMContentLoaded", function() {
    const searchBtn = document.querySelector(".searchBtn");
    
    searchBtn.addEventListener("click", function(e) {
        e.preventDefault();
        
        // 입력값 가져오기
        const keywordInput = document.querySelector("input[name='keywordInput']");
        const typeSelect = document.querySelector("select[name='typeSelect']");
        
        const keyword = keywordInput.value.trim();
        const types = typeSelect.value;
        
        // 검색 파라미터 구성
        const params = new URLSearchParams({
            page: '1',   // 검색하면 항상 1페이지부터
            size: '10'
        });
        
        // 검색 조건이 있을 때만 추가
        if (types && types !== '' && keyword && keyword !== '') {
            params.set("types", types);
            params.set("keyword", keyword);
        }
        
        // 검색 실행
        window.location.href = '/board/list?' + params.toString();
    });
});

💡 검색 시 항상 1페이지: 새로운 검색 결과는 처음부터 보여야 하니까요!


🔄 검색 조건 유지 흐름

전체 흐름을 정리해볼게요:

flowchart TB
    A["1. 사용자가 'Spring' 검색"] --> B["2. types=T, keyword=Spring 전송"]
    B --> C["3. Controller가 Service 호출"]
    C --> D["4. MyBatis 동적 SQL 실행"]
    D --> E["5. JSP에 dto 전달"]
    E --> F["6. 검색 결과 + 조건 표시"]
    F --> G["7. 페이지 클릭 시 조건 유지"]
    G --> B

📝 핵심 정리

기능사용 기술핵심 포인트
목록 출력<c:forEach>dto.boardDTOList 반복
XSS 방지<c:out>HTML 이스케이프
검색 조건 유지selected 속성EL 삼항 연산자
페이지 이동JavaScriptURLSearchParams 사용
검색 실행JavaScript항상 page=1로 이동

🚀 다음 편 예고

**6편: 댓글 기능과 예외 처리 마스터하기**에서는:

  • ReplyController REST API
  • ReplyService 예외 처리
  • 커스텀 예외 클래스
  • AJAX 댓글 기능

을 알아볼 예정입니다!


📚 시리즈 목차