이번 글에서는 Servlet이 컨트롤러를 담당하고 JSP는 뷰만 담당하는 모델2로 방명록 애플리케이션을 만들어보도록 하겠습니다.

이전글 모델1과 비교하는 것이 가장 큰 목적입니다.



1. 방명록 개요

모델2의 흐름은 다음과 같습니다.

  • 모든 요청은 Servlet에서 받습니다.
  • DAO를 통해 DB에 접근해서 요청을 처리한 후, 클라이언트에게 JSP로 응답합니다.
    • 여기에 forward라는 개념이 있는데, 이는 뒤에서 살펴보도록 하겠습니다.



프로젝트 구성은 모델1과 같습니다.

  • 방명록을 작성하는 입력 form 페이지
  • 게시글 리스트 페이지
  • 삭제하기 위해 비밀번호를 입력하는 페이지

모델1과 모델2로 똑같은 애플리케이션을 구현하는 이유는 모델1이 왜 불편한지, Servlet이 왜 중요한지를 알기 위함입니다.



디렉토리 구조는 조금 변경이 있지만, guestbook 테이블은 그대로입니다.





2. 디렉토리 구조 살펴보기

  • 패키지에 대해
    • Contoller 패키지를 만든 이유는 여러 요청에 대한 Servlet을 만들어서 처리하기 위함입니다.
    • 애플리케이션의 규모가 커지면 방명록에 대한 요청 뿐만 아니라, 회원가입, 로그인 등 여러가지 요청을 처리하게 될텐데, 그 때 LoginServlet , SignupSerlvet 등 Servlet 파일을 저장하기 위해서입니다.
    • 또 테이블 매핑을 위한 VO , DAO 객체들을 모아두기 위해 VO, DAO 패키지도 있습니다.
  • Web-Content - views 폴더에 대해
    • JSP 파일은 WebContent에 직접 위치하지 않고, WEB-INF 폴더 하위의 views폴더에 위치합니다.
      • JSP 파일이 WEB-INF 폴더 하위에 위치하게 되면, 위부에서 직접 접근을 할 수 없게 됩니다. ( 보안을 위해 )
      • 애플리케이션 내부에서는 WEB-INF 폴더에 접근할 수 있으므로, Servlet이 요청을 처리한 후 응답할 페이지를 WEB-INF 폴더에 접근하여 응답하는 구조입니다.



이제 모델2로 방명록 애플리케이션을 구현해보도록 하겠습니다.

GuestBookDAO , GuestBookVO는 이전 글과 내용이 모두 같습니다.

그럴 수 밖에 없는 것이 guestbook 테이블은 변함이 없고, 애플리케이션이 수행하는 서비스도 변함이 없기 때문입니다.

GuestBookDAO와 GuestBookVo 클래스는 코드가 길기 때문에 이전 글을 참고해주시기 바랍니다. ( 링크 )





3. GuestBookServlet 

먼저 Serlvet 클래스를 작성해보도록 하겠습니다.


GuestBookServlet.java

package Controller;


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

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 DAO.GuestBookDAO;
import VO.GuestBookVO;


@WebServlet("/gb")
public class GuestBookServlet extends HttpServlet {
private static final long serialVersionUID = 1L;


public GuestBookServlet() {
super();
}


protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");

String actionName = request.getParameter("a");

if("deleteform".equals(actionName)) {
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/deleteform.jsp");
rd.forward(request, response);
}
else if("add".equals(actionName)) {
request.setCharacterEncoding("utf-8");

String name = request.getParameter("name");
String pwd = request.getParameter("pwd");
String content = request.getParameter("content");

GuestBookVO vo = new GuestBookVO();

vo.setName(name);
vo.setPwd(pwd);
vo.setContent(content);

GuestBookDAO dao = new GuestBookDAO();
dao.insert(vo);

response.sendRedirect("/guestbook2/gb");
}
else if("delete".equals(actionName)) {
request.setCharacterEncoding("utf-8");

Integer no = Integer.parseInt(request.getParameter("no"));
String Inputpwd = request.getParameter("pwd");

GuestBookDAO vo = new GuestBookDAO();
String dbPwd = vo.getPwd(no);
String parseInputPwd = vo.getInputPwd(Inputpwd);

if( dbPwd.equals(parseInputPwd)){
vo.delete(no);
}

response.sendRedirect("/guestbook2/gb");
}
else {
GuestBookDAO dao = new GuestBookDAO();
List<guestbookvo> list = dao.getList();

request.setAttribute("list", list);

RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/index.jsp");
rd.forward(request, response);
}
}


protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

}

Servlet 클래스에서 짚고 넘어가야 할 부분이 2가지가 있습니다.

  • 1) actionName

    • reqeust.getParameter() 메서드는 요청 파라미터 값을 얻을 수 있는 메서드입니다. ( get 방식, post 방식 둘 다 얻을 수 있음 )
      • 예를 들어, 클라이언트가 localhost:8080/guestbook2/gb?a=add 으로 요청을 했다면, actionName의 값은 add가 됩니다.
    • 이렇게 파라미터 값을 받는 이유는 Servlet에게 어떤 요청인지 알려줘서 각 요청에 맞게 처리를 나누기 위해서입니다.
      • 그래서 보시는 바와 같이 actionName 값에 따라 if - else if - else 로 처리되고 있는 것을 볼 수 있습니다.
      • 사용자가 URI 파라미터 a에 이상한 값을 넣을 경우 또는 기본 값으로 처리하기 위한 action은 else{ } 부분에 처리했습니다.
    • 뒤에서 JSP 파일을 작성할 때 보시겠지만 <a> 태그이든 <form> 태그이든 URI 파라미터 a의 값을 넘겨줍니다. 따라서 Servlet은 적절한 처리를 수행할 수 있게 됩니다.
    • 물론 RESTful하게 localhost:8080/guestbook2/gb/add와 같이 작성해도 됩니다.
      • 하지만 URL을 파싱하는 작업이 번거로워서 간단하게 애플리케이션을 구성하고자, 파라미터로 action 이름을 넘겨주는 방식을 사용했습니다.

  • 2) forward와 redirect
    • redirect는 HTTP 응답 코드로 302를 클라이언트에 넘겨줘서, 해당 URL로 다시 요청 하도록 합니다.
      • 즉, 모든 자원을 반환한 상태로 새롭게 요청을 하는 것이죠.
    • 반면 forward는 요청 받은 자원을 그대로 유지한 채로 제어권을 넘기는 request의 연장성입니다.
      • 즉, Servlet에서 요청을 처리한 후 그 자원을 그대로 유지한채 JSP에 넘겨줘서 JSP가 이를 사용할 수 있도록 하는 메서드입니다.
      • Servlet이 컨트롤러 역할을 할 수 있게 하는 핵심 메서드라고 할 수 있습니다.
    • 여기서 주의 할 점은 JSP 파일이 /WEB-INF 폴더 내부에 있으므로 외부에 보이지 않습니다.
      • 따라서 redirect() 메서드를 호출할 때, 인자로 /WEB-INF 폴더로 시작하는 경로명을 작성하는 것이 아닌 URL을 작성해야 합니다.


이 두 가지 외의 코드는 DAO 객체의 메서드를 실행하는 것이 전부입니다.

보시는 바와 같이 Servlet은 요청을 받아서 처리를 하고 forward 또는 redirect 하는 역할을 담당합니다.




4. JSP - 방명록 쓰기 / 리스트 

이제 JSP 파일을 구현해보도록 하겠습니다.

먼저 방명록을 쓰거나 리스트를 보여주는 페이지입니다.


index.jsp

<%@page import="VO.GuestBookVO"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
List<GuestBookVO> list = (List<GuestBookVO>)request.getAttribute("list");
%>

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>방명록</title>
</head>
<body>
<form action="/guestbook2/gb" method="post">
<input type="hidden" name="a" value="add">
<table border="1" width="500">
<tr>
<td>이름</td><td><input type="text" name="name"></td>
<td>비밀번호</td><td><input type="password" name="pwd"></td>
</tr>
<tr>
<td colspan=4><textarea name="content" cols=60 rows=5></textarea></td>
</tr>
<tr>
<td colspan=4 align=right><input type="submit" VALUE=" 확인 "></td>
</tr>
</table>
</form>
<br>
<% if(list != null){
for(GuestBookVO vo : list){ %>
<table width="510" border="1">
<tr>
<td><%= vo.getNo() %></td>
<td><%= vo.getName() %></td>
<td><%= vo.getRegDate() %></td>
<td><a href="/guestbook2/gb?a=deleteform&no=<%= vo.getNo() %>">삭제</a></td>
</tr>
<tr>
<td><%= vo.getContent() %></td>
</tr>
</table>
<br>
<% } %>
<% } %>
</body>
</html>
  • 모델1의 index.jsp와 크게 다른 것은 없는데, <%  %> 부분이 줄었다는 것을 확인할 수 있습니다.

  • Servlet에서 request.setAttribute("list", list); 메서드를 호출하여, request 객체에 애트리뷰트를 추가했습니다.
    • 이 자원을 공유하기 위해 forward() 메서드를 호출했고, index.jsp에 제어권을 넘겨줬습니다.
    • 따라서 index.jsp에서는 DAO에 접근할 필요없이 Servlet이 준비해둔 list 객체만 가져와서 쓰기만 하면 됩니다. 이것이 모델2의 핵심입니다.




5. JSP - 삭제

마지막으로 삭제 form을 구현해보겠습니다.


deleteform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String no = request.getParameter("no");
%>
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>방명록</title>
</head>
<body>
<form method="post" action="/guestbook2/gb">
<input type='hidden' name="no" value="<%= no %>">
<input type='hidden' name="a" value="delete">
<table>
<tr>
<td>비밀번호</td>
<td><input type="password" name="pwd"></td>
<td><input type="submit" value="확인"></td>
</tr>
</table>
</form>
<a href="/guestbook2/gb">메인으로 돌아가기</a>
</body>
</html>
  • deleteform.jsp은 모델1과 동일합니다.




이제 프로젝트 폴더를 톰캣에 등록하고,

URL에 localhost:8080/guestbook/gb 으로 요청을 보내서 테스트를 해보세요.

참고로 Servlet에서 어노테이션을 /gb으로 정의했기 때문에 guestbook/gb 로 요청합니다.




이상으로 모델2로 방명록 애플리케이션을 만들어 보았습니다.

다음 글에서는 모델1과 모델2를 비교하고, Factory 디자인 패턴으로 모델2를 리팩토링 해보도록 하겠습니다.


댓글 펼치기 👇
  1. gut 2020.05.22 14:46

    쉽게 정리가 잘 되어있네요~ 잘 봤습니다.

  2. Favicon of https://ppowerppush.tistory.com @ppowerppush 2020.10.02 15:51 신고

    적절한 타이밍에 너무 좋은 MVC 모델 공부가 되었어요
    굿굿