프로젝트/게시판

[SpringBoot] 게시판 9. 게시글 검색 기능 - 비동기 구현

qkzkdo 2023. 8. 10. 23:12
728x90

구현 순서

1. index.html 

2. board-search.html

3. BoardSearchDTO

4. BoardController

5. BoardService

6. BoardServiceProcess

7. BoardMapper

8. board-mapper.xml

9. 게시글 검색 구현 화면

 

 

 

1-1. index.html

index에 검색 영역 코드 작성

<div>
    <form id="form-search">
        <select name="columnName">
            <option value="1">제목</option>
            <option value="2">내용</option>
            <option value="3">제목+내용</option>
        </select>
        <input type="search" name="query" placeholder="검색어를 입력하세요">
        <button type="button" id="btn-search">검색</button>
    </form>
</div>

위 코드를 작성하면 뷰에서 아래 사진처럼 검색할 수 있는 화면이 보인다.

 

 

1-2. index <head>에 자바스크립트 코드 추가

<script type="text/javascript">
    $(function(){
        boardList();
        $("form").submit(function(event){
            event.preventDafault();
        });
        $("#btn-search").click(function(){
            btnSearchClicked(1);
        });

    });

    function boardList(){
        $.ajax({
            url: "/board-list",
            type: "GET",
            success:function(result){
                $("#board-list").html(result);
            }
        });
    };

    function btnSearchClicked(page){

        var data=$("#form-search").serialize()+"&page="+page;
        $.ajax({
            url:"/board/search",
            type: "PATCH",
            data:data,
            success:function(result){
                $("#board-list").html(result);
            }
        });
    }
</script>
  • $("form").submit(function(event){ ... })는 페이지 내의 모든 <form> 요소가 제출될 때의 기본 동작인 페이지 새로고침을 막는다. 즉, 폼 제출 시 페이지가 새로고침되지 않도록 한다.
  • $("#btn-search").click(function(){ ... })는 페이지 내의 id가 btn-search인 요소가 클릭되었을 때의 동작을 설정한다. 클릭 시 btnSearchClicked 함수를 호출하며, 페이지 번호를 1로 전달하여 검색 결과를 가져온다.

 

  • btnSearchClicked 함수는 매개변수 page를 받아와서 실행된다.
  • $("#form-search").serialize()는 HTML 폼의 내용을 직렬화하여 URL 쿼리 문자열 형태로 반환한다.
  • "&page="+page는 페이지 번호를 URL 쿼리 문자열에 추가한다.
  • success:function(result) : result를 사용하여 #board-list 요소의 내용을 변경한다.

 

 

2. board-search.html

검색된 리스트들을 보여줄 html 작성

<body>
    <a href="/">HOME</a>
    <a href="/board/new">글쓰기</a>
    <p>검색게시판</p>
    <table border="1">
        <tr>
            <td>글번호</td>
            <td>제목</td>
            <td>조회수</td>
            <td>수정일</td>
            <td>삭제</td>
        </tr>
        <tr th:each="dto : ${list}">
            <td th:text="${dto.no}">글번호</td>
            <td>
                <a th:href="|/board/${dto.no}|" th:text="${dto.title}">제목</a>
            </td>
            <td th:text="${dto.readCount}">조회수</td>
            <td th:text="${#temporals.format(dto.updateDate, 'yyyy-MM-dd HH:mm:ss')}">수정일</td>
            <td>
                <form th:action="|/board/delete/${dto.no}|" method="post" onsubmit="return delete_event()">
                    <input type="hidden" name="_method" value="DELETE"> 
                    <button type="submit">삭제</button>
                    <script type="text/javascript">
                        function delete_event(){
                            if(confirm("정말 삭제하시겠습니까?") == true){
                                return true; 
                             }else{
                                return false; 
                             }
                        }
                    </script>
                </form>
            </td>
        </tr>

    </table>
    
    <div id="page-wrap" style="display: flex">
        <div th:if="${from>1}">
            <p th:value="${from-1}">
                <a class="page" href="#" >&lt;</a>
            </p>
        </div>
        <div  style="display: flex">
            <p th:each="pno:${#numbers.sequence(from,to)}" th:value="${pno}" >
                <a class="page" href="#" th:text="${pno}"></a>
            </p>
        </div>
        <div th:if="${to < tot}">
            <p th:value="${to+1}">
                <a class="page" href="#" >&gt;</a>
            </p>
        </div>
    </div>

    <script type="text/javascript">
        $("#board-list").find(".page").click(function(event){
            event.preventDefault();
            btnSearchClicked($(this).parent().attr("value"));
        });
    </script>
</body>
  • $("#board-list")는 id가 board-list인 요소를 선택한다. 이 요소 내에서 .page 클래스를 가진 요소를 찾는다.
  • .page 클래스를 가진 요소는 페이지 번호를 클릭할 때 작동한다.
  • 클릭 이벤트가 발생하면 event.preventDefault()를 호출하여 기본 동작(링크 이동)을 차단한다.
  • btnSearchClicked($(this).parent().attr("value"))는 클릭한 페이지 번호의 값을 btnSearchClicked 함수에 전달하여 해당 페이지의 검색 결과를 가져온다.

 

 

3. BoardSearchDTO

@Getter
@Setter
public class BoardSearchDTO {
	private int columnName;
	private String query;
	private int page=1;
}

 

 

4. BoardController

PATCH 메서드 URL과 매핑할 메서드 작성

//게시글 검색
@ResponseBody
@PatchMapping("/board/search")
public ModelAndView boardSearchList(@RequestParam(defaultValue = "1") int page, BoardSearchDTO dto) {
    return service.boardSearchProcess(page, dto);
}

 

 

5. BoardService

BoardService 인터페이스에 메서드 선언

//게시글 검색
ModelAndView boardSearchProcess(int page, BoardSearchDTO dto);

 

 

6. BoardServiceProcess

BoardService 인터페이스의 메서드 구현

//게시글 검색
@Override
public ModelAndView boardSearchProcess(int page, BoardSearchDTO dto) {
    ModelAndView mv = new ModelAndView("board/board-search");
    if(page<1) page = 1;
    int limit = 5;
    int offset = (page-1)*limit;

    List<BoardDTO> result = mapper.findAllBySearch(dto, limit, offset);
    mv.addObject("list", result);

    int rowCount = mapper.countAllBySearch(dto); // 총 게시글 수
    int tot = rowCount/limit; //총 페이지 수
    if(rowCount % limit > 0) tot++; //나머지가 존재하면 1 증가

    int RANGE = 5; //한 페이지에 보여질 페이지 번호 개수
    int rangeNo = page/RANGE; //출력되는 페이지 번호 범위를 나타냄
    if(page % RANGE > 0)rangeNo++; // 나머지 페이지가 있으면 페이지 번호를 증가시킨다

    int to = RANGE * rangeNo; //한 페이지의 마지막 번호 
    int from = to - RANGE+1; //한 페이지의 시작 번호
    //1~5 6~10 11~15 16~20
    if(to > tot) to = tot; 

    mv.addObject("from", from); //한 페이지의 시작 번호를 "from" 이름으로 mv에 저장
    mv.addObject("to", to); //한 페이지의 마지막 번호를 "to" 이름으로 mv에 저장
    mv.addObject("tot", tot); //총 페이지 개수를 "tot" 이름으로 mv에 저장

    return mv;
}
  • RowBounds 객체를 생성하여 조회할 범위를 설정한다. 검색 결과를 페이징 처리하기 위해 사용된다.
  • ModelAndView 객체를 생성하여 "board/board-search" 뷰를 사용하도록 설정한다.

 

 

7. BoardMapper

XML 파일에 정의된 쿼리를 호출할  메서드 선언

//검색
List<BoardDTO> findAllBySearch(@Param("dto") BoardSearchDTO dto, @Param("limit") int limit, @Param("offset") int offset);

int countAllBySearch(BoardSerachDTO dto);

 

 

8. board-mapper.xml

동적 쿼리 작성

<select id="findAllBySearch" resultType="com.pha.project.dto.BoardDTO">
    select * from board
    <where>
        <if test="dto.columnName != 0 and dto.columnName == 1 or dto.columnName == 3">
            title like '%${dto.query}%'
        </if>
        <if test="dto.columnName != 0 and dto.columnName == 2 or dto.columnName == 3">
            or content like '%${dto.query}%'
        </if>
    </where>
    order by no desc
    limit #{limit} offset #{offset}
</select>


<select id="countAllBySearch" resultType="int">
    select count(*) from board
    <where>
        <if test="columnName != 0 and columnName == 1 or columnName == 3">
            title like '%${query}%'
        </if>
        <if test="columnName != 0 and columnName == 2 or columnName == 3">
            or content like '%${query}%'
        </if>
    </where>
</select>
  • <where> 절은 쿼리 내에서 조건문을 묶어주는 역할. 조건이 하나 이상일 경우에만 조건문을 추가한다.
  • <if> : 조건문을 적용하는 요소. test 속성에 지정된 조건식이 참일 경우 내부의 SQL 코드가 적용된다.
  • dto.columnName != 0 and dto.columnName == 1 or dto.columnName == 3 : 검색 조건이 제목(columnName == 1)이거나 제목과 내용(columnName == 3)에 해당하는 경우에 해당한다.
  • title like '%${dto.query}%': 제목에 검색어가 포함된 경우를 찾는다.
  • content like '%${dto.query}%': 내용에 검색어가 포함된 경우를 찾는다.

 

 

9. 게시글 검색 구현 화면

728x90