본문 바로가기
JSP

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(18) - 키워드를 이용한 검색 만들기

by 김마리님 2020. 6. 12.

이전 게시글은 여기 >>

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(17) - 쿠키를 이용한 새로고침 시 조회수 무한 증가 방어

 

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(17) - 쿠키를 이용한 새로고침 시 조회수 무한 증

이전 게시글은 여기 >> Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(16) - 게시판 페이징과, 페이징 버튼 중 시작과 끝 버튼 비활성화하기 > Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(15)

itstudy-mary.tistory.com

 

네X버 블로그도 그렇고, 티스토리도 검색 기능을 제공한다

그러니까 우리 블로그도 검색 기능을 만들어보자.

검색기능을 도와줄 폼은 부트스트랩에서 찾을 것이다.

 

https://www.w3schools.com/bootstrap4/bootstrap_navbar.asp

 

Bootstrap 4 Navigation Bar

Bootstrap 4 Navigation Bar Navigation Bars A navigation bar is a navigation header that is placed at the top of the page: Logo Link Link Disabled Basic Navbar With Bootstrap, a navigation bar can extend or collapse, depending on the screen size. A standard

www.w3schools.com

부트 스트랩 중 navbar에서 검색 기능이 있는 것을 찾는다.

다.. 다 가져올 필요는 없고 이것만 가져오면 된다.

이제 본격적으로 만들어보자.

먼저 키워드에 검색이 되면 넘어갈 수 있는 액션부터 구현하자.

 

- home.jsp 의 검색창 코드

	<div class="col-md-12 m-2">
		<form class="form-inline justify-content-end" 
		action="/blog/board">
			<input type="hidden" name="cmd" value="search"/>
			<input type="hidden" name="page" value="0"/>
			<input class="form-control mr-sm-2" name="keyword" type="text"  placeholder="Search">
			<button class="btn btn-success" type="submit">Search</button>
		</form>
	</div>

이 때 좀 주의할 점이 있는데, action에 암만 /blog/board/cmd=search&page=0 이렇게 걸어도 막상 누르면 쿼리 스트링은 죄다 사라지고 keyword만 쿼리스트링으로 남아버리기 때문에, hidden 속성을 이용해 함께 날려주자.

 

이제 날아간 요청을 처리해야겠지

지금까지 했다면 순서를 잘 알고 있을 것이다. 라우터 구현- 액션 구현 - (필요하면 DB구현)

 

- BoardController.java 의 라우터 부분

	public Action router(String cmd) {
		if(cmd.equals("home")) {
			// 홈페이지로 이동
			return new BoardHomeAction(); //Board의 목록
		}else if(cmd.equals("write")) {
			// 글쓰기 페이지로 이동
			return new BoardWriteAction();
		}else if(cmd.equals("writeProc")) {
			// 글쓰기 정보 넘기기
			return new BoardWriteProcAction();
		}else if(cmd.equals("detail")) { 
			//상세보기
			return new BoardDetailAction();
		}else if(cmd.equals("update")) {
			//수정페이지
			return new BoardUpdateAction();
		}else if(cmd.equals("search")) {
			//검색
			return new BoardSearchAction();
		}else if(cmd.equals("updateProc")) {
			//수정로직
			return new BoardUpdateProcAction();
		}else if(cmd.equals("delete")) {
			//삭제로직
			return new BoardDeleteProcAction();
		}
		return null;
		
	}

 

다음, 액션 구현

이 액션은 홈 액션과 별 차이는 없지만.. 이제 검색 키워드가 있다는 것이 차이이다. 그렇기 때문에 홈 액션과 차이는 두지 않으면서 이전 DB repository에 매개변수만 차이를 두며 오버로딩을 한다.

 

 

- BoardSearchAction.java

package com.cos.blog.action.board;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.cos.blog.action.Action;
import com.cos.blog.model.Board;
import com.cos.blog.repository.BoardRepository;
import com.cos.blog.util.HtmlParser;
import com.cos.blog.util.Script;

public class BoardSearchAction implements Action{

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 1. DB연결해서 Board 목록 다 불러와서
		int page=Integer.parseInt(request.getParameter("page"));
		String keyword=request.getParameter("keyword");
		if(request.getParameter("keyword")==null ||
			request.getParameter("keyword")=="") {
			Script.back("검색 키워드가 없습니다.", response);
			return;
		}
		BoardRepository boardRepository = BoardRepository.getInstance();

		// 2. 검색해서 서치로 가져오기.
		List<Board> boards = boardRepository.findAll(page,keyword);
		int totalCount=boardRepository.countAll(keyword);

		// 본문 짧게 가공하기
		for (Board board : boards) {
			String preview = HtmlParser.getContentPreview(board.getContent());
			board.setContent(preview);
		}

		request.setAttribute("boards", boards);
		request.setAttribute("totalCount", totalCount);

		RequestDispatcher dis = 
				request.getRequestDispatcher("home.jsp");
		dis.forward(request, response);
	}
} 

 

공백은 검색되지 않게 먼저 유효성 검사는 해야겠다.

 

homeAction에서는 페이징을 위한 페이지만 매개변수로 받았는데(count는 변수가 필요없었지. 다 가져오면 되니까!), 이번엔 필요한 두 가지에 keyword 변수를 추가해서 가져온다.

 

- BoardRepository.java의 오버로딩 된 매서드들.

	public List<Board> findAll(int page,String keyword) {
		StringBuilder sb=new StringBuilder();
		sb.append("select /*+ INDEX_DESC(BOARD SYS_C008232)*/id, userid, title, content, readcount, createdate ");
		sb.append("FROM board ");
		sb.append("where title like ? or content like ? "); //여기선 %안먹음.
		sb.append("OFFSET ? ROWS FETCH NEXT 3 ROWS ONLY");

		final String SQL=sb.toString();
		List<Board> boards=new ArrayList<>();
		try {
			conn=DBConn.getConnection();
			pstmt = conn.prepareStatement(SQL);
			//물음표 완성하기
			pstmt.setString(1, "%"+keyword+"%");
			pstmt.setString(2, "%"+keyword+"%");
			pstmt.setInt(3, page*3);

			rs=pstmt.executeQuery();
			//while
			while(rs.next()) {
				Board board=new Board(
						rs.getInt("id"),
						rs.getInt("userId"),
						rs.getString("title"),
						rs.getString("content"),
						rs.getInt("readCount"),
						rs.getTimestamp("createDate")
				);		
				boards.add(board);
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(TAG+"findAll(page,keyword) : "+e.getMessage());
		}finally {
			DBConn.close(conn, pstmt);
		}

		return null;
	}
    
    	public int countAll(String keyword) {
		final String SQL="select count(*) from board where title like ? or content like ?";
		int totalCount=0;
		try {
			conn=DBConn.getConnection();
			pstmt = conn.prepareStatement(SQL);
			//물음표 완성하기
			pstmt.setString(1, "%"+keyword+"%");
			pstmt.setString(2, "%"+keyword+"%");

			rs=pstmt.executeQuery();
			//while
			if(rs.next()) {
				totalCount=rs.getInt("count(*)");
			}
			return totalCount;
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(TAG+"count(keyword) : "+e.getMessage());
		}finally {
			DBConn.close(conn, pstmt);
		}

		return -1;
	}

 

필자는 쿼리를 바로 변수를 넣는게 아니라 쿼리에 ?를 넣고, pstmt를 사용해서 set자료구조 함수를 이용해서 넣는데, 주의할 점이 있다.

「모델 2 형식은 JSP와 서블릿 모두 사용합니다.」 라는 글을 검색하는데, 이 제목을 모두 넣어야 할까? 아니면 "모델", "JSP", "서블릿" 이라는 단어만 들어가도 검색 되는게 좋을까? 다른 사람들은 모르겠지만 필자는.. 후자가 편하다. 그러니까, 검색될 키워드 변수에  %변수%를 이용해 변수 앞뒤로 무슨 글자가 들어가든 검색 되도록 조치할 예정이다.

 

이 때, pstmt를 사용한다고 해서  %?% 로 넣으면 인식 못하니까 pstmt.setString(x, %+받아오는 매개변수+%) 로 검색되도록 해야한다.

당연히 % %를 사용하니까 데이터베이스에선 like로 검색해야겠다.

 

또, 필자는 딱히 검색페이지를 만들지 않고 home.jsp에서 검색 페이지와 홈페이지를 모두 구현할 예정인데, 이것을 위해서 jstl의 c:set함수를 이용하자.

 

- home. jsp

<%@page import="com.cos.blog.model.Users"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="include/nav.jsp" %>

<c:choose>
	<c:when test="${empty param.keyword }">
		<c:set var="pageNext" value="/blog/board?cmd=home&page=${param.page+1 }"/>
	</c:when>
	<c:otherwise>
		<c:set var="pageNext" value="/blog/board?cmd=search&page=${param.page+1 }&keyword=${param.keyword }"/>
	</c:otherwise>
</c:choose>
<c:choose>
	<c:when test="${empty param.keyword }">
		<c:set var="pagePrev" value="/blog/board?cmd=home&page=${param.page-1 }"/>
	</c:when>
	<c:otherwise>
		<c:set var="pagePrev" value="/blog/board?cmd=search&page=${param.page-1 }&keyword=${param.keyword }"/>
	</c:otherwise>
</c:choose>

<div class="container">

	<div class="col-md-12 m-2">
		<form class="form-inline justify-content-end" 
		action="/blog/board">
			<input type="hidden" name="cmd" value="search"/>
			<input type="hidden" name="page" value="0"/>
			<input class="form-control mr-sm-2" name="keyword" type="text"  placeholder="Search">
			<button class="btn btn-success" type="submit">Search</button>
		</form>
	</div>

	<c:forEach var="board" items="${boards}">
	<div class="card m-2" style="width:100%">
	  <div class="card-body">
	    <h4 class="card-title">${board.title}</h4>
	    <p class="card-text">${board.content}</p>
	    <a href="/blog/board?cmd=detail&id=${board.id }" class="btn btn-primary">상세보기</a>
	  </div>
	</div>
	</c:forEach>
<br/>

	<ul class="pagination justify-content-center">
		<c:choose>
			<c:when test="${param.page==0 }">
				<li class="page-item disabled"><a class="page-link" href="${pageScope.pagePrev }">Previous</a></li>
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="${pageScope.pagePrev }">Previous</a></li>
			</c:otherwise>
		</c:choose>
		<c:choose>
			<c:when test="${param.page<=totalCount/3-1 }">
				<li class="page-item"><a class="page-link" href="${pageScope.pageNext }">Next</a></li>
			</c:when>
			<c:otherwise>
				<li class="page-item disabled"><a class="page-link" href="${pageScope.pageNext }">Next</a></li>
			</c:otherwise>
		</c:choose>

바로, 받아오는 파라메터 값에 keyword가 있을때와 없을 때의 uri를 다르게 지정하는 방법이다. 검색되는 블로그 페이지 글만 조정하면 되는 문제니까..!

 

키워드 파라메터가 없으면 cmd=home을 가져오고, 키워드 파라메터가 있으면 cmd=search가 실행되는 형식이다. 이걸 위해서 c:set을 이용해 c:when 내부에 다시 분기할 필요가 없도록 만든다. 

 

결과물

 

(왼쪽은 검색 안할때, 오른쪽은 검색할 때. uri가 변한 것을 확인할 수 있다!)

 

반응형