MVC 패턴의 이해
Model
dao dto
데이터베이스 담당
클라이언트 url로 요청 -> contriller 받음(유용한 패턴참고)
model1
model2
command를 기능에 따라 분리한다.
계시판의 전체적인 설계
게시판에서만 사용하는 DAO (서비스 별로 존재하게 된다.) JDBC
DTO와 DB는 구조가 같다. (mybatis)
FrontController에서 .do 패턴 적용
DB생성
게시판 관리 테이블 생성하기
board.sql
use webstoredb;
drop table if exists board;
create table board(
num int not null auto_increment, -- 게시글 순번
id varchar(10) not null, -- 회원 아이디
name varchar(20) not null, -- 회원 이름
subject varchar(100) not null, -- 게시글 제목
content text not null, -- 게시글 내용
registDay varchar(30), -- 게시글 등록 일자
hit int, -- 게시글 조회수
ip varchar(20), -- 게시글 등록 시 IP
primary key(num)
)default charset=utf8mb4;
select * from board;
desc board;
메뉴 수정 - 게시판 추가(.do : 확장자 패턴)
파일 단위 링크가 아니라 서블릿을 찾아서 실행하는 구조. 컨트롤러가 해당된 서버를 확인해서 화면흐름으로 실행
파라미터 추가 가능 get방식 -> 게시판은 게시글이 누적됨 한페이지에서 다 보여주지 않고 5개 10개씩 보여줌.
menu.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
request.setCharacterEncoding("UTF-8");
String sessionId = (String)session.getAttribute("sessionId"); //로그인 여부 판단
%>
<nav class="navbar navbar-expand navbar-dark bg-dark"><!-- 네비게이션 바를 만듬 -->
<div class="container"> <!-- container라는 클래스부터 내용을 표시함 -->
<div class="navbar-header"> <!-- 네비게이션 헤더 -->
<a class="navbar-brand" href="${pageContext.request.contextPath}/welcome.jsp">HOME</a>
</div>
<!-- 네비게이션 바 요소를 추가등록 -->
<div>
<ul class="navbar-nav mr-auto"> <!-- mr-auto : 마진공간 자동설정 -->
<!--sessionId가 null이면 로그인으로 링크, 로그인 되어있다면-->
<c:choose>
<c:when test="${empty sessionId }">
<li class="nav-item"><a class="nav-link" href='<c:url value="/member/loginMember.jsp" />'>로그인</a></li>
<li class="nav-item"><a class="nav-link" href='<c:url value="/member/addMember.jsp" />'>회원가입</a></li>
</c:when>
<c:otherwise>
<li style="padding-top:7px; color:white;">[<%=sessionId %> 님]</li>
<li class="nav-item"><a class="nav-link" href='<c:url value="/member/logoutMember.jsp" />'>로그아웃</a></li>
<li class="nav-item"><a class="nav-link" href='<c:url value="/member/UpdateMember.jsp" />'>회원 수정</a></li>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${sessionId ne 'admin' }">
<li class="nav-item"><a href="${pageContext.request.contextPath}/products.jsp" class="nav-link">상품목록</a></li>
</c:when>
<c:otherwise>
<li class="nav-item"><a href="${pageContext.request.contextPath}/products.jsp" class="nav-link">상품목록</a></li>
<li class="nav-item"><a href="${pageContext.request.contextPath}/addProduct.jsp" class="nav-link">상품등록</a></li>
<li class="nav-item"><a href="${pageContext.request.contextPath}/editProduct.jsp?edit=update" class="nav-link">상품수정</a></li>
<li class="nav-item"><a href="${pageContext.request.contextPath}/editProduct.jsp?edit=delete" class="nav-link">상품삭제</a></li>
</c:otherwise>
</c:choose>
<li class="nav-item"><a class="nav-link" href="${pageContext.request.contextPath}/boardListAction.do?pageNum=1">게시판</a></li>
</ul>
</div>
</div>
</nav>
확장자가 *.do로 끝나는 요청(파일)들을 컨트롤러로 보내기위해 매핑
.do를 찾을 수 있게 정의 (주석) -> 패키지 만들기 kr.gov.mvc.controller -> BoardController.java(servlet) 만들기
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>WebStore</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 404에러가 발생하면 자동으로 exceptionNoPage.jsp 페이지 보여주게 된다. -->
<error-page>
<error-code>404</error-code>
<location>/exceptionNoPage.jsp</location>
</error-page>
<!-- 시큐리티 역할 추가 -->
<security-role>
<description>관리자</description>
<role-name>admin</role-name>
</security-role>
<!-- 제약조건 추가 -->
<security-constraint>
<display-name>WebStore Security</display-name>
<web-resource-collection> <!-- 웹자원 제약조건 설정 -->
<web-resource-name>WebStore</web-resource-name> <!-- 프로젝트 이름 -->
<description></description>
<url-pattern>/addProduct.jsp</url-pattern> <!-- 상품등록 페이지 -->
<url-pattern>/editProduct.jsp</url-pattern> <!-- 상품수정 페이지 -->
</web-resource-collection>
<auth-constraint> <!-- 권한 제약조건 설정 -->
<description>권한 관리자명</description>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config> <!-- 시큐리티 인증 설정 -->
<auth-method>FORM</auth-method> <!-- 폼 인증처리 방식 -->
<form-login-config>
<form-login-page>/login.jsp</form-login-page> <!-- 로그인이 성공할 경우 -->
<form-error-page>/login_failed.jsp</form-error-page> <!-- 로그인이 실패할 경우 -->
</form-login-config>
</login-config>
<!-- 필터 적용 -->
<filter>
<filter-name>LogFilter</filter-name>
<filter-class>kr.gov.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LogFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 필터(파일기록) 적용 -->
<filter>
<filter-name>LogFileFilter</filter-name>
<filter-class>kr.gov.filter.LogFileFilter</filter-class>
<init-param>
<param-name>filename</param-name> <!-- 로그 기록을 남길 파일 경로 지정 -->
<param-value>C:\\workspace-jsp\\log\\webstore.log</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LogFileFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 확장자가 *.do로 끝나는 요청(파일)들을 컨트롤러로 보내기위해 매핑 -->
<servlet>
<servlet-name>BoardController</servlet-name>
<servlet-class>kr.gov.mvc.controller.BoardController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BoardController</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
MVC 구조화 - 패키지 추가 폴더 추가
DB 연동하기 (dao에 생성할수 있지만 별개로 추가함)
DBConnection.java
package kr.gov.mvc.database;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBConnection {
public static Connection getConnection() {
Connection conn = null;
String url = "jdbc:mysql://localhost:3306/webstoredb?serverTimezone=UTC";
String user = "root";
String password = "7496";
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(url, user, password);
System.out.println("데이터베이스 연결되었습니다.");
} catch (Exception e) {
System.out.println("데이터베이스 연결되지 않았습니다.");
e.printStackTrace();
}
return conn;
}
}
DTO 만들기 DB와 유사하게 정의
BoardDTO.java
package kr.gov.mvc.model;
/*
num int not null auto_increment, -- 게시글 순번
id varchar(10) not null, -- 회원 아이디
name varchar(20) not null, -- 회원 이름
subject varchar(100) not null, -- 게시글 제목
content text not null, -- 게시글 내용
registDay varchar(30), -- 게시글 등록 일자
hit int, -- 게시글 조회수
ip varchar(20), -- 게시글 등록 시 IP
*/
public class BoardDTO {
private int num;
private String id;
private String name;
private String subject;
private String content;
private String registDay;
private int hit;
private String ip;
public BoardDTO() {
// TODO Auto-generated constructor stub
}
//getter, setter
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRegistDay() {
return registDay;
}
public void setRegistDay(String registDay) {
this.registDay = registDay;
}
public int getHit() {
return hit;
}
public void setHit(int hit) {
this.hit = hit;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}
프론트 컨트롤러 작성
BoardController.java(servlet)
BoardController.java(servlet)
package kr.gov.mvc.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*@WebServlet("/BoardController")*/
public class BoardController extends HttpServlet {
private static final long serialVersionUID = 1L;
public BoardController() {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
private void actionDo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("actionDo");
String uri = request.getRequestURI();
System.out.println("URI : " + uri);
String contextPath = request.getContextPath();
System.out.println("contextPath : " + contextPath);
String command = uri.substring(contextPath.length());
System.out.println("command : " + command);
response.setContentType("text/html; charse=utf-8");
request.setCharacterEncoding("UTF-8");
}
}
DAO 작성
CRED요청을 실행하게됨 (dbconn은 따로 빼놓은 상태)
싱글톤 패턴 적용
DB의 요청을 받는다.
1. board 테이블에 레코드 가져오는 메서드 (페이지당 표시되는 게시물)
게시물 페이지 숫자, 페이지당 게시물 수, 제목, 글쓴이, 검색단어 파라미터로 추가
2. board 테이블에 레코드 개수를 가져오는 메서드 (전체 게시물 수)
BoardDAO.java
package kr.gov.mvc.model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import kr.gov.mvc.database.DBConnection;
public class BoardDAO {
private Connection conn = null; //DB접속시 필요한 객체
private PreparedStatement pstmt= null;
private ResultSet rs = null;
private ArrayList<BoardDTO> dtos = null;
private static BoardDAO instance;
public BoardDAO() {
// TODO Auto-generated constructor stub
}
public static BoardDAO getInstance() { //싱글톤 패턴으로 BoardDAO객체 하나만 만들어서 리턴.
if(instance == null) {
instance = new BoardDAO();
}
return instance;
}
//board 테이블에 레코드 가져오는 메서드
//page : 게시물 페이지 숫자, limit : 페이지당 게시물 수, items : 제목, 본문, 글쓴이, text : 검색어
public ArrayList<BoardDTO> getBoardList(int page, int limit, String items, String text) {
int totalRecord = getListCount(items, text); //board테이블의 전체 레코드 개수
int start = (page - 1) * limit; //선택 page이전까지의 레코드 개수
int index = start + 1; //선택 page 시작 레코드(게시물)
String sql = "";
dtos = new ArrayList<BoardDTO>();
if(items == null && text == null) { //파라미터로 넘어오는 검색기능이 두군데 모두 아무값이 없는 경우
sql = "select * from board order by num desc";
}
else {
sql = "select * from board where " +items+ "like '%"+text+"%' order by num desc"; //매개변수가 파라미터로 넘어오는 값으로 검색
}
try {
conn = DBConnection.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while(rs.absolute(index)) { //가령 6페이지를 보고있다가 1페이지를 클릭하게되면 -> 마이너스값이 되면안됨.
BoardDTO board = new BoardDTO();
board.setNum(rs.getInt("num"));
board.setId(rs.getString("id"));
board.setName(rs.getString("name"));
board.setSubject(rs.getString("subject"));
board.setContent(rs.getString("content"));
board.setRegistDay(rs.getString("registDay"));
board.setHit(rs.getInt("hit"));
board.setIp(rs.getString("ip"));
dtos.add(board);
//인덱스가 가져올 데이터건수 보다 작다면
if(index < (start + limit) && index <= totalRecord) {
index ++;
}
else {
break;
}
}
}catch (SQLException e){
System.out.println("getBoardList() 예외" + e.getMessage());
e.printStackTrace();
}finally {
try {
if(rs != null) rs.close();
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
} catch (Exception e2) {
System.out.println("getBoardList()의 close() 호출 예외" + e2.getMessage());
e2.printStackTrace();
}
}
return dtos;
}
//board 테이블에 레코드 개수를 가져오는 메서드
public int getListCount(String items, String text) {
int count = 0;
String sql = "";
//파라미터로 넘어오는 검색기능 두군데 모두 아무값도 없는 경우
if(items == null && text == null) {
sql = "select count(*) from board";
}
else {
sql = "select count(*) from board where " +items+ "like '%"+text+"%' "; //파라미터로 넘어오는 값으로 검색
}
try {
conn = DBConnection.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
if(rs.next()) {
count = rs.getInt(1);
}
} catch (SQLException e) {
System.out.println("getListCount() 예외" + e.getMessage());
e.printStackTrace();
}finally {
try {
if(rs != null) rs.close();
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
} catch (Exception e2) {
System.out.println("getListCount()의 close() 호출 예외" + e2.getMessage());
e2.printStackTrace();
}
}
return count;
}
}
프론트 컨트롤러 수정 - 커맨드 패턴 분기하는 코드 추가
BoardController.java
boardListAction.do
BoardController.java
package kr.gov.mvc.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*@WebServlet("/BoardController")*/
public class BoardController extends HttpServlet {
private static final long serialVersionUID = 1L;
public BoardController() {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
private void actionDo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("actionDo");
String uri = request.getRequestURI();
System.out.println("URI : " + uri);
String contextPath = request.getContextPath();
System.out.println("contextPath : " + contextPath);
String command = uri.substring(contextPath.length());
System.out.println("command : " + command);
response.setContentType("text/html; charse=utf-8");
request.setCharacterEncoding("UTF-8");
if(command.equals("/boardListAction.do")) {
System.out.println("--------------------------------");
System.out.println("/boardListAction.do 페이지 호출");
System.out.println("--------------------------------");
}
}
}
커맨드 인터페이스 생성
BCommand.java (interface) - 상속을 받아 오버라이드 하는것을 제시해준다. (선언부만 정의)
package kr.gov.mvc.command;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface BCommand {
//구현 객체에서 해당하는 기능을 각각 다르게 작성하게 될것임(overriding)
public void execute(HttpServletRequest request, HttpServletResponse response);
}
커맨드 생성
DAO에 getBoardList 호출 -> DTO에 당아서 (dtos) 리턴 -> 결과를 받아서 커맨드에서 저장처리 한후 프론트컨트롤러에 다시 던져준다.
BListCommand.java
package kr.gov.mvc.command;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import kr.gov.mvc.model.BoardDAO;
import kr.gov.mvc.model.BoardDTO;
public class BListCommand implements BCommand {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) { // 게시판 리스트를 직접적으로 가져오는 커맨드 객체
BoardDAO bDao = BoardDAO.getInstance();
ArrayList<BoardDTO> boardlist = new ArrayList<>();
int pageNum = 1;
int limit = 5; //한페이지에 나타낼 게시글 수
if(request.getParameter("pageNum") != null) {
pageNum = Integer.parseInt(request.getParameter("pageNum")); //페이지 값이 null이 아니라면 해당 페이지를 숫자로 변환하여 저장함.
}
String items = request.getParameter("items"); //제목,본문, 글쓴이
String text = request.getParameter("text"); //검색어
int totalRecord = bDao.getListCount(items, text); //DB저장되어 있는 게시글 총 갯수
boardlist = bDao.getBoardList(pageNum, limit, items, text); //DB에 저장되어 있는 실제 게시글 가져옴
//홈페이지 수 구하기
int totalPage = 1;
if(totalPage % limit == 0) {
totalPage = totalRecord / limit;
Math.floor(totalPage);
}else {
totalPage = totalRecord / limit;
Math.floor(totalPage);
totalPage += 1;
}
//request객체에 각각 해당하는 값 저장
request.setAttribute("pageNum", pageNum);
request.setAttribute("totalPage", totalPage);
request.setAttribute("totalRecord", totalRecord);
request.setAttribute("boardList", boardlist);
}
}
프론트 컨트롤러 작성
BListCommand 호출하기
dao dtos 리턴한 결과를 BListCommand에 저장해 놓아서 컨트롤러에서도 공유하고 있다.
BoardController,java
package kr.gov.mvc.controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import kr.gov.mvc.command.BCommand;
import kr.gov.mvc.command.BListCommand;
/*@WebServlet("/BoardController")*/
public class BoardController extends HttpServlet {
private static final long serialVersionUID = 1L;
public BoardController() {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("doGet");
actionDo(request, response);
}
private void actionDo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("actionDo");
String uri = request.getRequestURI();
System.out.println("URI : " + uri);
String contextPath = request.getContextPath();
System.out.println("contextPath : " + contextPath);
String command = uri.substring(contextPath.length());
System.out.println("command : " + command);
response.setContentType("text/html; charse=utf-8");
request.setCharacterEncoding("UTF-8");
BCommand com = null;
String viewPage = null;
//command 패턴에 따라서 분기함
if(command.equals("/boardListAction.do")) {
System.out.println("--------------------------------");
System.out.println("/boardListAction.do 페이지 호출");
System.out.println("--------------------------------");
com = new BListCommand();
com.execute(request, response);
viewPage = "./board/list.jsp";
}
else if(command.equals("/boardWriteForm.do")) {
}
//위의 분기문에서 설정된 view(.jsp)파일로 페이지 이동
RequestDispatcher rDispatcher = request.getRequestDispatcher(viewPage);
rDispatcher.forward(request, response);
}
}
view(jsp)에서도 Blistcommand 객체 사용할 수 있게 함 RequestDispatcher 사용
JSP 파일 생성
list.jsp
<%@page import="kr.gov.mvc.model.BoardDTO"%>
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
String sessionId = (String)session.getAttribute("sessionId");
ArrayList<BoardDTO> boardList = (ArrayList<BoardDTO>)request.getAttribute("boardList");
int totalRecord = ((Integer)request.getAttribute("totalRecord")).intValue();
int totalPage = ((Integer)request.getAttribute("totalPage")).intValue();
int pageNum = ((Integer)request.getAttribute("pageNum")).intValue();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/bootstrap.min.css">
</head>
<body>
<jsp:include page="../menu.jsp"/>
<div class="jumbotron">
<div class="container">
<h1 class="display-3">게시판</h1>
</div>
</div>
<div class="container">
<form action="" method="post">
<div class="text-right">
<h2><span class="badge badge-danger">전체 건수 : <%=totalRecord %></span></h2>
</div>
</form>
</div>
</body>
</html>
에러처리 하기
getBoardList() 예외Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY.
java.sql.SQLException: Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY.
BoardDAO.java 수정
try {
conn = getConnection(); //커넥션 얻기
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
rs = pstmt.executeQuery(); //DB에 저장되어 있는 상품 모두 가져와 ResultSet에 담음
JSP 출력확인 해보기
결과 확인용 더미데이터 추가
board.sql
use webstoredb;
drop table if exists board;
create table board(
num int not null auto_increment, -- 게시글 순번
id varchar(10) not null, -- 회원 아이디
name varchar(20) not null, -- 회원 이름
subject varchar(100) not null, -- 게시글 제목
content text not null, -- 게시글 내용
registDay varchar(30), -- 게시글 등록 일자
hit int, -- 게시글 조회수
ip varchar(20), -- 게시글 등록 시 IP
primary key(num)
)default charset=utf8mb4;
select * from board;
desc board;
insert into webstoredb.board
(id, name, subject, content, registDay, hit, ip)
values ('gumi', '이순신', '설맞이', '새해복많이..', sysdate(), 1, '');