today_is

[ jsp ] MVC패턴 - Servlet 활용, 게시판 페이징 및 검색 본문

java

[ jsp ] MVC패턴 - Servlet 활용, 게시판 페이징 및 검색

ye_rang 2023. 12. 25. 23:33

오늘의 목표

앞서 MVC패턴의 로직 분리에 대해서 배웠으니, 

페이징을 적용한 게시판 목록 및 검색 기능을 구현해보자

 


BoardDTO

 
idx number default board2_seq.nextval primary key
title varchar2(500) not null  
writer varchar2(100) not null  
content varchar2(4000) not null  
image varchar2(500)    
ipaddr varchar2(100) not null  
viewCount number default 0  
writeDate date default sysdate  

 

 

 

BoardDAO

package board2;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class BoardDAO {

	private Connection conn;
	private PreparedStatement pstmt;
	private ResultSet rs;
	
	private Context init;
	private DataSource ds;
	
	
	private static BoardDAO instance = new BoardDAO();
	
	public static BoardDAO getInstance() {
		return instance;
	}
	
	private BoardDAO() {
		try {
			init = new InitialContext();
			ds = (DataSource) init.lookup("java:comp/env/jdbc/oracle");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void close() {
		try {
			if(rs != null) rs.close();
			if(pstmt != null) pstmt.close();
			if(conn != null) conn.close();
			
		} catch (Exception e) {}
	}
	
	
	private BoardDTO mapping(ResultSet rs) throws SQLException {
		BoardDTO dto = new BoardDTO();
		
		dto.setIdx(rs.getInt("idx"));
		dto.setTitle(rs.getString("title"));
		dto.setWriter(rs.getString("writer"));
		dto.setContent(rs.getString("content"));
		dto.setImage(rs.getString("image"));
		dto.setIpaddr(rs.getString("ipaddr"));
		dto.setViewCount(rs.getInt("viewCount"));
		dto.setWriteDate(rs.getDate("writeDate"));
		dto.setDeleted(rs.getInt("deleted"));
		
		return dto;
	}
	
	
	//	게시글 목록 불러오기
	public List<BoardDTO> selectList(String search, Paging paging) {
		ArrayList<BoardDTO> list = new ArrayList<>();
		
		String sql = "select * from board2 "
				+ " where "
				+ "		   deleted = 0 and"
				+ "        (title like '%' || ? || '%' or "
				+ "        writer like '%' || ? || '%' or "
				+ "        content like '%' || ? || '%')  "
				+ " order by idx desc"
				
				+ " offset ? rows"
				+ " fetch next ? rows only";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, search);
			pstmt.setString(2, search);
			pstmt.setString(3, search);
			
			pstmt.setInt(4, paging.getOffset());	//	페이징
			pstmt.setInt(5, paging.getFetch());
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				list.add(mapping(rs));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return list;
		
	}
	
	
	//	게시글 조회 1개 (ip 주소를 일부 가리기)
	public BoardDTO selectOne(int idx) {
		BoardDTO dto = null;
		
		String sql = "select * from board2 where idx = ?";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, idx);
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				dto = mapping(rs);
				dto.setIpaddr(maskIPaddr(dto.getIpaddr()));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return dto;
	}
	
	private String maskIPaddr(String src) {	// 아이피 주소 원본(DB에서 가져온 값)
		String dst = "";
		int dotCount = 0;
		for(int i = 0; i < src.length(); i++) {
			char ch = src.charAt(i);
			if(ch == '.') dotCount += 1;
			if(dotCount >= 2 && '0' <= ch && ch <= '9') {
				dst += '*';
			}
			else {
				dst += ch;
			}
		}
		return dst;
	}
	
	
	//	게시글 작성
	public int insert(BoardDTO dto) {
		int row = 0;
		
		String sql = "insert into board2 (title, writer, content, image, ipaddr) "
				+ "  values (?, ?, ?, ?, ?)";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getTitle());
			pstmt.setString(2, dto.getWriter());
			pstmt.setString(3, dto.getContent());
			pstmt.setString(4, dto.getImage());
			pstmt.setString(5, dto.getIpaddr());
			
			row = pstmt.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return row;
		
	}
	
	
	//	 삭제(update)	: 삭제 처리하고 나서
	//	리스트를 불러올때 deleted 값이 0 인 항목만 불러오기 
	//	(== 진짜 삭제가 아님. deleted = 1 인 애들만 보이지 않게)   
	//	deleted = 1 - deleted : 업데이트 할때마다 deleted 의 값이 1 <-> 0 로 왔다갔다하기 
	public int delete(int idx) {
		int row = 0;
		
		String sql = "update board2 set "
				+ "	deleted = 1 - deleted "
				+ "	where idx = ?";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, idx);
			row = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return row;
	}
	
	
	
	//	내가 쓴 글만 볼 수 있는 list
	public List<BoardDTO> selectListByWriter(String userid) {
		ArrayList<BoardDTO> list = new ArrayList<>();
		
		String sql = "select * from board2 "
				+ " where writer = ?";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, userid);

			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				list.add(mapping(rs));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return list;
		
	}
	
	
	//	페이징
	//	게시글 개수 불러오기
	public int selectCount(String search) {
		int count = 0;
		
		String sql = "select count(*) from board2 "
				+ " where "
				+ "		   deleted = 0 and"
				+ "        (title like '%' || ? || '%' or "
				+ "        writer like '%' || ? || '%' or "
				+ "        content like '%' || ? || '%')  ";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, search);
			pstmt.setString(2, search);
			pstmt.setString(3, search);
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				count = rs.getInt(1);	
			}
		
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return count;
		
	}
	
	
	//	조회수 증가
	public int updateViewCount(int idx) {
		int row = 0;
		String sql = "update board2 set viewCount = viewCount + 1 where idx = ?";
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, idx);
			row = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return row;
	}	

	
	
	//	댓글 작성
	public int insertReply(ReplyDTO dto) {
		int row = 0;
		String sql = "insert into reply2 (parent_idx, reply_depth, board_idx, writer, content) "
				+	" values (? ,? ,? ,? ,?)";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, dto.getParent_idx());
			pstmt.setInt(2, dto.getReply_depth());
			pstmt.setInt(3, dto.getBoard_idx());
			pstmt.setString(4, dto.getWriter());
			pstmt.setString(5, dto.getContent());
			row = pstmt.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			close();
		}
		return row;
	}
	
	
	//	댓글 목록 불러오기
	public List<ReplyDTO> selectReplyList(int idx) {
		ArrayList<ReplyDTO> list = new ArrayList<ReplyDTO>();
		String sql = "select * from reply2"
				+ " where board_idx = ?"
				+ " start with parent_idx = 0"
				+ " connect by prior idx = parent_idx"
				+ " order SIBLINGS by idx";
		
		try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, idx);
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				ReplyDTO dto = new ReplyDTO();
				dto.setBoard_idx(rs.getInt("board_idx"));
				dto.setContent(rs.getString("content"));
				dto.setIdx(rs.getInt(idx));
				dto.setParent_idx(rs.getInt("parent_idx"));
				dto.setReply_depth(rs.getInt("reply_depth"));
				dto.setWriteDate(rs.getDate("writeDate"));
				dto.setWriter(rs.getString("writer"));
				list.add(dto);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			close();
		}
		
		
		return list;
	}
}

 

Paging

package board2;

public class Paging {
	
	private int page;			
	private int perPage;		
	private int boardCount;
	private int offset;	//	offset : 건너뛸 페이지 개수
	private int fetch;	//	fetch  : 그 다음 불러올 게시글 개수 (== perPage)
	
	
	private int pageCount;	//	전체 페이지 개수
	private int section;	//	페이지를 10개씩 묶어서 하나의 구역으로 설정하고 0부터 시작
	private boolean prev;	//	이전 구역이 있으면 true
	private boolean next;	//	다음 구역이 있으면 true
	
	private int begin;	//	구역 시작 페이지
	private int end;	//	구역 끝 페이지(pageCount 보다 크면 안된다 !! == 게시글 개수보다 클 수가 없음) 
	
	
	//	생성자를 대신할 static method
	//	싱글톤으로 객체를 생성하면 모두 같은 페이지만 볼 수 밖에 없음
	//	사용자에 따라서 페이지 번호를 전달받아서 
	//	각기 다르게 처리해주어야하기 때문이다 
	public static Paging newInstance(int page, int boardCount) {
		return new Paging(page, boardCount);
		
	}
	
	
	
	//	생성자 : 페이지와 보드 카운트를 전달 받았을때
	//	나머지 컬럼의 값을 채울 수 있게 
	private Paging(int page, int boardCount) {	//	boardCount 에서 page 내용만 불러오기
		this.page = page;
		this.boardCount = boardCount;
		
		perPage = 15;	//	화면에 출력될 페이지 개수
		offset = (page - 1) * perPage;	
		fetch = perPage;
		
		pageCount = boardCount / perPage;
		pageCount += (boardCount % perPage != 0) ? 1: 0;
		section = (page - 1) / 10;	//	구역 : 1 ~ 10(== 0) ,  11 ~ 20(== 1) 
		
		begin = section * 10 + 1;
		end = begin + 9;
		
		prev = section != 0;	//	이전은 섹션이 0이 아닐때만 존재 (prev = true)
		next = pageCount > end;
		
		if(end > pageCount) {	//	end는 pageCount 보다 크면 안된다 !! == 게시글 개수보다 클 수가 없음
			end = pageCount;
			next = false;	//	끝 페이지이기 때문에 더 이상 다음 페이지가 없음(false 로 next 를 출력시키지 않음)
		}
	}
	
	
	public int getPage() {
		return page;
	}
	public void setPage(int page) {
		this.page = page;
	}
	public int getPerPage() {
		return perPage;
	}
	public void setPerPage(int perPage) {
		this.perPage = perPage;
	}
	public int getBoardCount() {
		return boardCount;
	}
	public void setBoardCount(int boardCount) {
		this.boardCount = boardCount;
	}
	public int getOffset() {
		return offset;
	}
	public void setOffset(int offset) {
		this.offset = offset;
	}
	public int getFetch() {
		return fetch;
	}
	public void setFetch(int fetch) {
		this.fetch = fetch;
	}
	public int getPageCount() {
		return pageCount;
	}
	public void setPageCount(int pageCount) {
		this.pageCount = pageCount;
	}
	public int getSection() {
		return section;
	}
	public void setSection(int section) {
		this.section = section;
	}
	public boolean isPrev() {
		return prev;
	}
	public void setPrev(boolean prev) {
		this.prev = prev;
	}
	public boolean isNext() {
		return next;
	}
	public void setNext(boolean next) {
		this.next = next;
	}
	public int getBegin() {
		return begin;
	}
	public void setBegin(int begin) {
		this.begin = begin;
	}
	public int getEnd() {
		return end;
	}
	public void setEnd(int end) {
		this.end = end;
	}

}

 

 

 


 

Java코드 JSP
Servlet View
데이터를 가져오는 역할을 수행 처리한 데이터를 화면에 출력하는 역할

 

 

 BoardServlet 

 

데이터 준비 및 로직에 필요한 자바 객체는 미리 생성해두기
-> private BoardDAO boardDAO = BoardDAO.getInstance();

 

 

[ doGet이 실행될때의 결과 ]

: getRequestDispatcher 를 사용해서 url 값으로 다시 넘긴다

이때, forward 를 이용하여 req, res 를 같이 보내게 됨

-> req.getRequestDispatcher(prefix + "board" + suffix).forward(req, resp)

 

 

즉, 실행할때마다 search 값을 받아서 값들을 보내고,

board.jsp 를 통해 list 를 화면에 출력시킴 

 

package day22;

import java.io.IOException;

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 board2.BoardDAO;
import board2.Paging;


@WebServlet("/board")
public class BoardServlet extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	private final String prefix = "/WEB-INF/views/";
	private final String suffix = ".jsp";
	
	
	private BoardDAO boardDAO = BoardDAO.getInstance();
	
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		String search = req.getParameter("search");
		
		if(search == null) {
			search = "";
		}
		int count = boardDAO.selectCount();
		String paramPage = req.getParameter("page");
		int page = Integer.parseInt(paramPage == null ? "1" : paramPage);
		Paging paging = Paging.newInstance(page, count);
		req.setAttribute("list", boardDAO.selectList(search, paging));
		req.setAttribute("paging", paging);
		
		req.getRequestDispatcher(prefix + "board" + suffix).forward(req, resp);
	}

}

 

 

 

 board.jsp 

 : BoardServlet 에서 paging 객체를 넣어준다면  jsp에서도 페이징을 참조함

 

 

BoardServlet 에 있는 setAttribute  의 결과가 board.jsp의 c:forEach 로 출력된다

 

 

 

[ BoardServlet ] req.setAttribute("list", boardDAO.selectList(search, paging))

[ board.jsp ] <c:forEach var="dto" items="${list }">

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="cpath" value="${pageContext.request.contextPath }" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>


	<table id="boardList">
		<thead>
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성자</th>
			<th>조회수</th>
			<th>날짜</th>
		</tr>
		</thead>

		<c:forEach var="dto" items="${list }">	
			<tr>
				<td>${dto.idx }</td>
				<td><a href="${cpath }/view.jsp?idx=${dto.idx }">
					${dto.title }
					${not empty dto.image ? '💾' : '' }
					</a>
				</td>
				<td>${dto.writer }</td>
				<td>${dto.viewCount }</td>
				<td>${dto.writeDate }</td>		
			</tr>
		</c:forEach>
	</table>
	
	<div class="center">
		<c:if test="${paging.prev }">
			<a href="${cpath }/board?page=${paging.begin - 10}&search=${param.search}">[이전]</a>
		</c:if>
	
		<c:forEach var="i" begin="${paging.begin }" end="${paging.end }">
			<a class="${paging.page == i ? 'bold' : '' }" 
			href="${cpath }/board?page=${i}&search=${param.search }">[${i }]</a>
		</c:forEach>
	
		<c:if test="${paging.next }">
			<a href="${cpath }/board?page=${paging.end + 1}&search=${param.search}">[다음]</a>
		</c:if>
		
		
	</div>
	
	<div>
		<form>
			<input type="search" name="search" value="${param.search }" placeholder="검색어 입력">
			<input type="submit" value="검색">
		</form>
	</div>
		
</body>
</html>

 

 


study_review

기존에는 jsp 페이지로 모든것을 작성하고,

form과 데이터 처리를 합쳐서 작성하는 경우가 많았지만

 

이번 실습을 통해서,

같은 기능을 구현하더라도 로직을 분리해서 구현하는 방법에 대해서 배우게 되었다.

 

 

앞전 실습에서는 doGet 과 doPost를 나누어서 다루었기 때문에

form의 제출 여부에 따라서 코드를 따로 작성했었다.

 

이번에는 doGet으로만으로도 search 와 paging 을 처리해보았다.

 

 

또한 Servlet에서 getParameter 를 이용하여 search 값을 받아올때,

search 가 빈문자열인 경우까지 생각해서, if문을 추가하여 코드의 완성도를 높일 수 있었다 !!

 

 

MVC패턴에 어느정도 익숙해졌으니 

다음은 Spring 을 본격적으로 배워볼 예정이다