<%@ 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}"><<</a></li>
<!-- 이전 목록 마지막 번호로 이동 -->
<li><a href="${url}${pagination.prevPage}${sURL}"><</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}">></a></li>
<!-- 끝 페이지로 이동 -->
<li><a href="${url}${pagination.maxPage}${sURL}">>></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>
// 상세조회 - 목록으로 버튼
(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;
}
}
}
})();
-- 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 '%유저일%';
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();
}
}
}
/** 검색 목록 조회 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 객체 반환
}
/** 특정 게시판에서 조건을 만족하는 게시글 수 조회 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;
}
<!-- 조건을 만족하는 게시글 수 조회 -->
<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>
검색한 키워드가 포함된 결과가 화면에 조회되는 모습을 볼 수 있다. :)