MVC Model 2는 JSP와 서블릿을 기반으로 하는 웹 애플리케이션 아키텍처 패턴으로, Model-View-Controller(MVC) 구조를 사용하여 서버 측에서 요청과 응답을 처리하는 방식입니다. MVC Model 2는 비즈니스 로직, 데이터, 프레젠테이션(화면) 로직을 명확히 분리하여, 유지보수와 확장성이 높은 구조를 제공합니다. 이 패턴은 Java 웹 애플리케이션에서 JSP와 서블릿의 역할을 명확히 나누고, 데이터 흐름을 구조화합니다.
아래는 간단한 게시판 예제로, 게시글 리스트를 조회하는 흐름을 통해 MVC Model 2의 구조를 이해해 보겠습니다.
public class BoardDTO {
private int id;
private String title;
private String content;
// 기본 생성자 및 getter, setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
BoardDTO
는 게시글 데이터를 저장하는 객체로, 데이터베이스에서 조회한 데이터를 JSP로 전달할 때 사용됩니다.import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class BoardDAO {
public List<BoardDTO> getAllPosts() throws Exception {
List<BoardDTO> postList = new ArrayList<>();
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/boarddb", "root", "password");
String sql = "SELECT * FROM board ORDER BY id DESC";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
BoardDTO post = new BoardDTO();
post.setId(rs.getInt("id"));
post.setTitle(rs.getString("title"));
post.setContent(rs.getString("content"));
postList.add(post);
}
} finally {
if (conn != null) conn.close();
}
return postList;
}
}
BoardDAO
는 데이터베이스에서 모든 게시글을 조회하여 BoardDTO
리스트로 반환합니다.import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet("/board/list")
public class BoardListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BoardDAO dao = new BoardDAO();
try {
List<BoardDTO> postList = dao.getAllPosts(); // 데이터 조회
request.setAttribute("postList", postList); // 보낼 데이터 지정
request.getRequestDispatcher("/WEB-INF/views/boardList.jsp").forward(request, response); // request, response 객체를 보낼 대상 파일 지정 및 포워딩
} catch (Exception e) {
throw new ServletException(e);
}
}
}
BoardListServlet
은 /board/list
URL 요청을 처리하는 서블릿으로, 게시글 리스트를 가져와 boardList.jsp
로 전달합니다.request.setAttribute
메서드를 통해 게시글 데이터를 뷰에 전달하고, RequestDispatcher
를 사용해 boardList.jsp
로 포워딩합니다.<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>게시글 목록</title>
</head>
<body>
<h2>게시글 목록</h2>
<table border="1">
<tr>
<th>번호</th>
<th>제목</th>
<th>내용</th>
</tr>
<c:forEach var="post" items="${postList}">
<tr>
<td>${post.id}</td>
<td>${post.title}</td>
<td>${post.content}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
boardList.jsp
는 서블릿에서 전달받은 postList
데이터를 출력하는 역할을 합니다.<c:forEach>
태그를 사용해 테이블 형식으로 화면에 표시됩니다.Controller
(서블릿)는 클라이언트 요청을 처리하고, Model
(DAO, DTO)은 데이터 처리를 담당하며, View
(JSP)는 사용자에게 화면을 출력합니다.MVC Model 2와 서블릿/JSP 관련하여 더 깊이 학습할 수 있는 공식 문서와 유익한 한글 자료를 추천합니다. 아래 자료들은 서블릿과 JSP, MVC 패턴의 기초부터 실습 예제까지 폭넓게 다루고 있어 학습에 도움이 될 것입니다.
BoardController.java
package org.example.controller;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.model2.*;
import java.io.IOException;
@WebServlet("/controller") // "/controller" URL로 매핑된 서블릿
public class BoardController extends HttpServlet {
// GET 요청이 들어올 때 처리하는 메서드
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doProcess(req, resp); // GET 요청을 doProcess로 위임
}
// POST 요청이 들어올 때 처리하는 메서드
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doProcess(req, resp); // POST 요청을 doProcess로 위임
}
// GET/POST 요청을 공통으로 처리하는 메서드
protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getParameter("path"); // 요청 파라미터에서 "path" 값을 가져옴
String url = ""; // 포워딩할 URL 초기화
Action action = null; // 실행할 Action 객체 초기화
// "path" 파라미터에 따라 적절한 Action 객체 생성 및 URL 설정
if (path == null || path.equals("list")) { // 게시글 목록 보기
action = new ListAction();
action.execute(req, resp); // 목록 조회 액션 실행
url = "/WEB-INF/views/board_list1.jsp"; // 목록 페이지로 포워딩 설정
} else if (path.equals("view")) { // 게시글 상세 보기
action = new ViewAction();
action.execute(req, resp); // 상세 보기 액션 실행
url = "/WEB-INF/views/board_view1.jsp"; // 상세 페이지로 포워딩 설정
} else if (path.equals("write")) { // 게시글 작성 페이지 이동
action = new WriteAction();
action.execute(req, resp); // 작성 페이지 액션 실행
url = "/WEB-INF/views/board_write1.jsp"; // 작성 페이지로 포워딩 설정
} else if (path.equals("write_ok")) { // 게시글 작성 완료
action = new WriteOkAction();
action.execute(req, resp); // 작성 완료 액션 실행
url = "/WEB-INF/views/board_write1_ok.jsp"; // 작성 완료 페이지로 포워딩 설정
} else if (path.equals("modify")) { // 게시글 수정 페이지 이동
action = new ModifyAction();
action.execute(req, resp); // 수정 페이지 액션 실행
url = "/WEB-INF/views/board_modify1.jsp"; // 수정 페이지로 포워딩 설정
} else if (path.equals("modify_ok")) { // 게시글 수정 완료
action = new ModifyOkAction();
action.execute(req, resp); // 수정 완료 액션 실행
url = "/WEB-INF/views/board_modify1_ok.jsp";// 수정 완료 페이지로 포워딩 설정
} else if (path.equals("delete")) { // 게시글 삭제 페이지 이동
action = new DeleteAction();
action.execute(req, resp); // 삭제 페이지 액션 실행
url = "/WEB-INF/views/board_delete1.jsp"; // 삭제 페이지로 포워딩 설정
} else if (path.equals("delete_ok")) { // 게시글 삭제 완료
action = new DeleteOkAction();
action.execute(req, resp); // 삭제 완료 액션 실행
url = "/WEB-INF/views/board_delete1_ok.jsp";// 삭제 완료 페이지로 포워딩 설정
} else {
// 처리되지 않은 path에 대한 추가 처리 필요 시 여기에 작성
}
// 최종적으로 지정된 URL로 요청을 포워딩하여 페이지 출력
RequestDispatcher dispatcher = req.getRequestDispatcher(url);
dispatcher.forward(req, resp);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>게시글 작성</title>
<link rel="stylesheet" type="text/css" href="./css/board.css">
<script type="text/javascript">
window.onload = function() {
// "쓰기" 버튼 클릭 시 이벤트 핸들러 설정
document.getElementById('wbtn').onclick = function () {
// 개인정보 수집 및 이용 동의 체크 여부 확인
if (document.wfrm.info.checked == false) {
alert('개인정보 수집 및 이용에 동의하셔야 합니다.');
return false;
}
// 필수 입력 항목 확인
if (document.wfrm.writer.value.trim() == '') {
alert('글쓴이를 입력하셔야 합니다.');
return false;
}
if (document.wfrm.subject.value.trim() == '') {
alert('제목을 입력하셔야 합니다.');
return false;
}
if (document.wfrm.password.value.trim() == '') {
alert('비밀번호를 입력하셔야 합니다.');
return false;
}
// 모든 유효성 검사를 통과하면 폼 전송
document.wfrm.submit();
};
};
</script>
</head>
<body>
<!-- 상단 제목과 경로 표시 영역 -->
<div class="con_title">
<h3>게시판</h3>
<p>HOME > 게시판 > <strong>게시글 작성</strong></p>
</div>
<!-- 게시글 작성 폼 -->
<div class="con_txt">
<form action="./controller" method="post" name="wfrm">
<input type="hidden" name="path" value="write_ok"/>
<div class="contents_sub">
<div class="board_write">
<table>
<tr>
<th class="top">글쓴이</th>
<td class="top">
<input type="text" name="writer" class="board_view_input_mail" maxlength="5" />
</td>
</tr>
<tr>
<th>제목</th>
<td>
<input type="text" name="subject" class="board_view_input" />
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<input type="password" name="password" class="board_view_input_mail" />
</td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="content" class="board_editor_area"></textarea>
</td>
</tr>
<tr>
<th>이메일</th>
<td>
<input type="text" name="mail1" class="board_view_input_mail"/> @
<input type="text" name="mail2" class="board_view_input_mail"/>
</td>
</tr>
</table>
<!-- 개인정보 수집 및 이용 동의 안내 -->
<table>
<tr>
<td style="text-align:left;border:1px solid #e0e0e0;background-color:#f9f9f9;padding:5px">
<div style="padding-top:7px;padding-bottom:5px;font-weight:bold;padding-left:7px;font-family: Gulim,Tahoma,verdana;">
※ 개인정보 수집 및 이용에 관한 안내
</div>
<div style="padding-left:10px;">
<div style="width:97%;height:95px;font-size:11px;letter-spacing:-0.1em;border:1px solid #c5c5c5;background-color:#fff;padding:14px 0 0 14px;">
1. 수집 항목: 회사명, 담당자명, 메일 주소, 전화번호 등 <br />
2. 수집 목적: 원활한 의사소통을 위한 경로 확보 <br />
3. 보유 기간: 조회를 위해 3개월 보관 후 파기 <br />
4. 기타 사항은 개인정보취급방침 준수
</div>
</div>
<div style="padding-top:7px;padding-left:5px;padding-bottom:7px;font-family: Gulim,Tahoma,verdana;">
<input type="checkbox" name="info" value="1" class="input_radio"> 개인정보 수집 및 이용에 대해 동의합니다.
</div>
</td>
</tr>
</table>
</div>
<!-- 하단 버튼 영역 -->
<div class="btn_area">
<div class="align_left">
<input type="button" value="목록" class="btn_list btn_txt02" style="cursor: pointer;" onclick="location.href='controller?path=list'" />
</div>
<div class="align_right">
<input type="button" id="wbtn" value="쓰기" class="btn_write btn_txt01" style="cursor: pointer;" />
</div>
</div>
</div>
</form>
</div>
</body>
</html>