커뮤니티 - 게시글 검색 기능 구현 (23.07.28)

·2023년 7월 28일
0

Server

목록 보기
34/35
post-thumbnail

📝 게시글 검색 기능


💡 VS Code

🔎 BoardList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- map에 저장된 값을 각각 변수에 저장 -->
<c:set var="boardName" value="${map.boardName}" />
<c:set var="pagination" value="${map.pagination}" />
<c:set var="boardList" value="${map.boardList}" />

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${boardName}</title>

    <link rel="stylesheet" href="${contextPath}/resources/css/boardList-style.css">

    <link rel="stylesheet" href="${contextPath}/resources/css/main-style.css">

    <script src="https://kit.fontawesome.com/4dca1921b4.js" crossorigin="anonymous"></script>
</head>
<body>
    <main>
		<jsp:include page="/WEB-INF/views/common/header.jsp"/>		

        <%-- 검색을 진행한 경우 key, query를 쿼리스트링 형태로 저장한 변수 생성 --%>
        <c:if test="${!empty param.key}">
            <c:set var="sURL" value="&key=${param.key}&query=${param.query}"/>
        </c:if>
        
        <section class="board-list">

            <h1 class="board-name">${boardName}</h1>

            <c:if test="${!empty param.key}">
                <h3 style="margin-left:30px;">"${param.query}" 검색 결과</h3>
            </c:if>

            <div class="list-wrapper">
                <table class="list-table">
                    <thead>
                        <tr>
                            <th>글번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                            <th>조회수</th>
                        </tr>
                    </thead>

                    <tbody>

                        <c:choose>
                            <c:when test="${empty boardList}">
                                <!-- 게시글 목록 조회 결과가 비어 있다면 -->
                                <tr>
                                    <th colspan="5">게시글이 존재하지 않습니다.</th>
                                </tr>
                            </c:when>

                            <c:otherwise>
                                <!-- 게시글 목록 조회 결과가 비어 있지 않다면 -->

                                <!-- 향상된 for문처럼 사용 -->
                                <c:forEach var="board" items="${boardList}">
                                    <tr>
                                        <td>${board.boardNo}</td>
                                        <td>
                                            <a href="detail?no=${board.boardNo}&cp=${pagination.currentPage}&type=${param.type}${sURL}">${board.boardTitle}</a>
                                        </td>
                                        <td>${board.memberNickname}</td>
                                        <td>${board.createDate}</td>
                                        <td>${board.readCount}</td>
                                    </tr>
                                </c:forEach>

                            </c:otherwise>

                        </c:choose>

                    </tbody>

                </table>
            </div>

            <div class="btn-area">

                <c:if test="${!empty loginMember}">
                    <!-- /community/board/write -->
                    <button id="insertBtn" onclick="location.href='write?mode=insert&type=${param.type}&cp=${param.cp}'">글쓰기</button>
                </c:if>

            </div>
            
            <div class="pagination-area">

                <!-- 페이지네이션 a태그에 사용될 공통 주소를 저장한 변수 선언 -->
                <c:set var="url" value="list?type=${param.type}&cp="/>

                <ul class="pagination">
                    <!-- 첫 페이지로 이동 -->
                    <li><a href="${url}1${sURL}">&lt;&lt;</a></li>

                    <!-- 이전 목록 마지막 번호로 이동 -->
                    <li><a href="${url}${pagination.prevPage}${sURL}">&lt;</a></li>

                    <!-- 범위가 정해진 일반 for문을 사용 -->
                    <c:forEach var="i" begin="${pagination.startPage}" end="${pagination.endPage}" step="1">

                        <c:choose>
                            <c:when test="${i == pagination.currentPage}">
                                <li><a class="current">${i}</a></li>
                            </c:when>

                            <c:otherwise>
                                <li><a href="${url}${i}${sURL}">${i}</a></li>
                            </c:otherwise>
                        </c:choose>

                    </c:forEach>

                    <!-- 다음 목록 시작 번호로 이동 -->
                    <li><a href="${url}${pagination.nextPage}${sURL}">&gt;</a></li>
                    
                    <!-- 끝 페이지로 이동 -->
                    <li><a href="${url}${pagination.maxPage}${sURL}">&gt;&gt;</a></li>
                </ul>
            </div>

            <!-- /board/list?type=1&cp=3 -->

            <!-- /board/list?type=1&key=t&query=안녕 -->
            <form action="list" method="get" id="boardSearch" onsubmit="return searchValidate()">
                <input type="hidden" name="type" value="${param.type}">

                <select name="key" id="search-key">
                    <option value="t">제목</option>
                    <option value="c">내용</option>
                    <option value="tc">제목+내용</option>
                    <option value="w">작성자</option>
                </select>

                <input type="text" name="query" id="search-query" placeholder="검색어를 입력해 주세요.">

                <button>검색</button>
            </form>

        </section>
    </main>

    <script src="${contextPath}/resources/js/board/board.js"></script>

    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>
</body>
</html>

🔎 Board.js

// 상세조회 - 목록으로 버튼

(function(){
    const goToListBtn = document.getElementById("goToListBtn");

    if(goToListBtn != null){ // 목록으로 버튼이 화면에 있을 때만 이벤트 추가

        goToListBtn.addEventListener("click", function(){
            
            // location 객체(BOM)

            // 문자열.substring(시작, 끝) : 시작 이상 끝 미만 인덱스까지 문자열 자르기

            // 문자열.indexOf("검색 문자열", 시작 인덱스)
            // : 문자열에서 "검색 문자열"의 위치(인덱스)를 찾아서 반환
            //   단, 시작 인덱스 이후부터 검색

            const pathname = location.pathname; // 주소상에서 요청 경로 반환
            //    /community/board/detail

            // 이동할 주소 저장
            let url = pathname.substring(0, pathname.indexOf("/", 1));
            //  /community

            url += "/board/list?"; //  /community/board/list?

            // URL 내장 객체 : 주소 관련 정보를 나타내는 객체
            // location.href : 현재 페이지 주소 + 쿼리스트링
            // URL.searchParams : 쿼리 스트링만 별도 객체로 반환
            
            // http://localhost:8080/community/board/detail?no=465&cp=4&type=1
            const params = new URL(location.href).searchParams;

            const type = "type=" + params.get("type"); // type=1
            let cp;
            
            if(params.get("cp") != ""){ // 쿼리스트링에 cp가 있을 경우
               cp = "cp=" + params.get("cp");
            } else{
                cp = "cp=1";
            }

            // 조립
            // /community/board/list?type=1&cp=1
            url += type + "&" + cp;

            // 검색 key, query가 존재하는 경우 url 추가
            if(params.get("key") != null){
                const key = "&key=" + params.get("key");
                const query = "&query=" + params.get("query");

                url += key + query; // url 뒤에 붙이기
            }

            // location.href = "주소"; -> 해당 주소로 이동
            location.href = url;
        });

    }

})();

// 검색 유효성 검사(검색어를 입력했는지 확인)
function searchValidate(){

    const query = document.getElementById("search-query");

    if(query.value.trim().length == 0){ // 미작성
        query.value = ""; // 빈칸
        query.focus();

        return false;
    }

    return true;
}

...

// 검색창에 이전 검색 기록 반영하기
(function(){
    const select = document.getElementById("search-key");

    const input = document.getElementById("search-query");

    // const option = select.children;
    const option = document.querySelectorAll("#search-key > option");

    if(select != null){ // 검색창 화면이 존재할 때만 코드 적용

        // 현재 주소에서 쿼리스트링(파라미터) 얻어오기
        const params = new URL(location.href).searchParams;

        // 얻어온 파라미터 중 key, query만 변수에 저장
        const key = params.get("key");
        const query = params.get("query");
        
        // input에 query값 대입
        input.value = query;

        // option을 반복 접근해서 value와 key와 같으면 selected 속성 추가
        for(let op of option){
            if(op.value == key){
                op.selected = true;
            }
        }
    }
})();

💡 Oracle DBMS

-- 2번 게시판에서 제목에 "50"이 포함된 게시글 수 조회
SELECT COUNT(*) FROM BOARD
JOIN MEMBER USING(MEMBER_NO)
WHERE BOARD_ST = 'N'
AND BOARD_CD = 2
-- AND BOARD_TITLE LIKE '%50%'; -- 제목
-- AND BOARD_CONTENT LIKE '%50%'; -- 내용
-- AND (BOARD_TITLE LIKE '%50%' OR BOARD_CONTENT LIKE '%50%'); -- 제목 + 내용
AND MEMBER_NICK LIKE '%유저일%';

💡 Eclipse

🔎 BoardListServlet.java

package edu.kh.community.board.controller;

import java.io.IOException;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import edu.kh.community.board.model.service.BoardService;

@WebServlet("/board/list")
public class BoardListServlet extends HttpServlet{
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {

			// 쿼리스트링 얻어오기 == 파라미터 얻어오기
			
			int type = Integer.parseInt(req.getParameter("type"));
			
			
			// nav 메뉴(공지사항, 자유게시판, 질문게시판) 선택 시
			// 쿼리스트링에 cp가 없음 --> cp = 1 고정
			int cp = 1;
			
			// 페이지네이션 번호 선택시
			// 쿼리스트링에 cp가 있음 --> cp = 쿼리스트링의 cp값
			if(req.getParameter("cp") != null) { // 쿼리스트링에 "cp"가 존재한다면
				cp = Integer.parseInt(req.getParameter("cp"));
			}
			
			BoardService service = new BoardService();
			
			// 게시판 이름, 페이지네이션 객체, 게시글 리스트를 한번에 반환하는 Service 호출
			Map<String, Object> map = null;
			
			if(req.getParameter("key") == null) { // 일반 목록 조회
				
				map = service.selectBoardList(type, cp);
				
			} else { // 검색 목록 조회
				
				String key = req.getParameter("key");
				String query = req.getParameter("query");
				
				map = service.searchBoardList(type, cp, key, query);
			}
			
			// request 범위로 map을 세팅
			req.setAttribute("map", map);
			
			String path = "/WEB-INF/views/board/boardList.jsp";
			
			RequestDispatcher dispatcher = req.getRequestDispatcher(path);
			dispatcher.forward(req, resp);

		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

🔎 BoardService.java

	/** 검색 목록 조회 Service
	 * @param type
	 * @param cp
	 * @param key
	 * @param query
	 * @return result
	 * @throws Exception
	 */
	public Map<String, Object> searchBoardList(int type, int cp, String key, String query) throws Exception{
		
		Connection conn = getConnection();
		
		// 기존 목록 조회 Service, DAO, SQL을 참고하면서 진행
		// 1. 게시판 이름 조회 DAO 호출
		String boardName = dao.selectBoardName(conn, type);
		
		// 2. SQL 조건절에 추가될 구문 가공(key, query 사용)
		String condition = null; // 조건
		
		switch(key) {
		case "t"	 : condition = " AND BOARD_TITLE LIKE '%" + query + "%' "; break;
		case "c"	 : condition = " AND BOARD_CONTENT LIKE '%" + query + "%' "; break;
		case "tc"	 : condition = " AND (BOARD_TITLE LIKE '%" + query + "%' OR BOARD_CONTENT LIKE '%" + query + "%') "; break;
		case "w"	 : condition = " AND MEMBER_NICK LIKE '%" + query + "%' "; break;
		}
		
		// 3-1. 특정 게시판에서 조건을 만족하는 게시글 수 조회
		int listCount = dao.searchListCount(conn, type, condition);
			
		// 3-2. listCount + 현재 페이지(cp)를 이용해 페이지네이션 객체 생성
		Pagination pagination = new Pagination(cp, listCount);
		
		// 4. 특정 게시판에서 조건을 만족하는 게시글 목록 조회
		List<Board> boardList = dao.searchBoardList(conn, pagination, type, condition);

		// 5. 결과값을 하나의 Map에 모아서 반환
		Map<String, Object> map = new HashMap<String, Object>();
		
		map.put("boardName", boardName);
		map.put("pagination", pagination);
		map.put("boardList", boardList);
		
		close(conn);

		return map; // Map 객체 반환
	}

🔎 BoardDAO.java

	/** 특정 게시판에서 조건을 만족하는 게시글 수 조회 DAO
	 * @param conn
	 * @param type
	 * @param condition
	 * @return listCount
	 */
	public int searchListCount(Connection conn, int type, String condition) throws Exception {
		
		int listCount = 0;
		
		try {

			String sql = prop.getProperty("searchListCount") + condition;
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setInt(1, type);
			
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
				listCount = rs.getInt(1);
			}
			
		} finally {
			close(rs);
			close(pstmt);
		}
		
		return listCount;
	}

	/** 특정 게시판에서 조건을 만족하는 게시글 목록 조회 DAO
	 * @param conn
	 * @param pagination
	 * @param type
	 * @param condition
	 * @return boardList
	 * @throws Exception
	 */
	public List<Board> searchBoardList(Connection conn, Pagination pagination, int type, String condition) throws Exception{
	      
	      List<Board> boardList = new ArrayList<Board>();
	      
	      try {
	         
	         String sql = prop.getProperty("searchBoardList1") 
	                  + condition
	                  + prop.getProperty("searchBoardList2");
	         
	         // BETWEEN 구문에 들어갈 범위 계산 
	         int start = ( pagination.getCurrentPage() - 1 ) * pagination.getLimit() + 1;
	         int end = start + pagination.getLimit() - 1;
	         
	         pstmt = conn.prepareStatement(sql);
	         
	         pstmt.setInt(1, type);
	         pstmt.setInt(2, start);
	         pstmt.setInt(3, end);
	         
	         rs = pstmt.executeQuery();
	         
	         while(rs.next()) {
	            
	            Board board = new Board();
	            
	            board.setBoardNo( rs.getInt("BOARD_NO"));
	            board.setBoardTitle( rs.getString("BOARD_TITLE"));
	            board.setMemberNickname( rs.getString("MEMBER_NICK"));
	            board.setCreateDate( rs.getString("CREATE_DT"));
	            board.setReadCount( rs.getInt("READ_COUNT"));
	            
	            boardList.add(board);
	         }
	         
	      }finally {
	         close(rs);
	         close(pstmt);
	      }
	      
	      return boardList;
	   }

🔎 board-sql.xml

	<!-- 조건을 만족하는 게시글 수 조회 -->
	<entry key="searchListCount">
		SELECT COUNT(*) FROM BOARD
		JOIN MEMBER USING(MEMBER_NO)
		WHERE BOARD_ST = 'N'
		AND BOARD_CD = ?
	</entry>

	<!-- 특정 게시판에서 조건을 만족하는 게시글 목록 조회 -->
	<entry key="searchBoardList1">
		SELECT * FROM(
		    SELECT ROWNUM RNUM, A.* FROM(
		        SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICK, 
		                TO_CHAR(CREATE_DT, 'YYYY-MM-DD')AS CREATE_DT, 
		                READ_COUNT 
		        FROM BOARD
		        JOIN MEMBER USING(MEMBER_NO)
		        WHERE BOARD_CD = ?
		        AND BOARD_ST = 'N'
	</entry>
   
	<entry key="searchBoardList2">
				ORDER BY BOARD_NO DESC
				    ) A
				)
				WHERE RNUM BETWEEN ? AND ?
	</entry>

💡 출력 화면

검색한 키워드가 포함된 결과가 화면에 조회되는 모습을 볼 수 있다. :)

profile
풀스택 개발자 기록집 📁

0개의 댓글