JSP

[JSP] MVC model2

shb 2022. 4. 6. 17:44

* MVC 모델

MVC란 Model, View, Controller를 뜻하는 용어로 개발 형태의 일종 입니다.
- Model :  데이터베이스와의 관계를 담당합니다. 클라이언트의 요청에서 필요한 자료를 데이터베이스로부터 추출하거나, 수정하여 Controller로 전달 합니다.
- View : 사용자한테 보여지는 UI 화면입니다. 주로 .jsp파일로 작성 하며, Controller에서 어떤 View 컴포넌트를 보여줄지 결정 합니다.
- Controller :  클라이언트의 요청을 받고, 적절한 Model에 지시를 내리며, Model에서 전달된 데이터를 적절한 View에 전달 합니다.
MVC로 분할하면, 추후 유지보수에 유리

 

MVC Model1 은 View(출력)와 Controller(로직)가 같이 있는 형태 - JSP가 '출력'과 '로직'을 둘 다 담당

* MVC - Model1 장단점
장점 : 구조가 단순하여 익히기가 쉽다.
        위와 같은 이유로 숙련된 개발자가 아니더라도 구현이 용이하다.
        유지보수보다는 빨리 구축해야하는 경우 사용가능.
단점 : 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드가 함께 섞이기 때문에 JSP 코드 자체가 복잡해진다
        JSP 코드에서 백엔드와 프론트엔드가 혼재되기 때문에 분업이 용이하지 않다.
        코드가 복잡해져 유지보수가 어렵다.

 

* MVC - Model2
MVC Model2 는  Model, View 그리고 Controller가 모두 ‘모듈’화 되어 있는 형태

장점 : 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드를 분리하기 때문에 JSP가 모델1에 비해 

        코드가 복잡하지 않다.
        뷰, 로직처리에 대한 분업이 용이하다.
        기능에 따라 분리되어있기 때문에 유지보수가 용이하다.

단점 : 구조가 복잡하여 습득이 어렵고 작업량이 많다.
         JAVA에 대한 깊은 이해가 필요하다.

 

 


패키지

com.command.write - MVC2의 '커맨드 객체'들을 담을 패키지

com.controller - MVC2에서 '컨트롤러 객체'들을 담을 패키지

com.lec.beans - DAO, DTO, 기타 서블릿

 

* 커맨드 인터페이스 작성

Command.java

패키지 : com.command.write
인터페이스 : Command 

 

* 컨트롤러 작성 - Controller 서블릿 

서블릿 생성 : DoController

URL매핑을 *.do 로 하면 URL 에 ~~.do 로 입력되면 이 서블릿이 요청된다.
(확장자 패턴이라고도 함)

 

* 컨트롤러 작성 -  actionDo 메소드 추가.
doGet, doPost 와 같은 형태의 actionDo 메소드 추가하고 doGet, doPost 메소드에서 actionDo 메소드 호출케 구조 변경

 

actionDo 동작 확인

*.do 형태이면 get 이든 post 방식이든 관계없이 FrontController 에게 넘어간다.

package com.controller;

import java.io.IOException;
import java.sql.SQLException;
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 com.command.write.Command;
import com.command.write.DeleteCommand;
import com.command.write.ListCommand;
import com.command.write.SelectCommand;
import com.command.write.UpdateCommand;
import com.command.write.ViewCommand;
import com.command.write.WriteCommand;

// *.do <-- 확장자 패턴 . URL 에 ~~.do 로  request 들어오면
// 아래 서블릿이 동작함.
@WebServlet("*.do")
public class DoController extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public DoController() {
        super();
    }

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

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

	private void actionDo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("actionDo() 호출");
		
		if(request.getMethod().equalsIgnoreCase("POST")) {
			request.setCharacterEncoding("UTF-8");
		}
		
		
		// URL로부터 URI, ContextPath, Command 분리
		String uri = request.getRequestURI();   // -->        /JSP18_MVC/aaa.do
		String conPath = request.getContextPath();  // -->    /JSP18_MVC
		String com = uri.substring(conPath.length());  // -->           /aaa.do

		// 테스트 출력
		System.out.println("uri: " + uri);  
		System.out.println("conPath: " + conPath);  
		System.out.println("com: " + com);
		
		// 컨트롤러는 커맨드에 따라, 로직을 수행하고
		// 그 결과를 내보낼 view 를 결정한다
		Command command = null;  // 어떠한 로직을 수행?
		String viewPage = null;  // 어떠한 페이지(뷰) 를 보여줄지?
		
		switch(com) {
		case "/list.do":
			command = new ListCommand();
			command.execute(request, response); // Command 를 수행한 결과는 request 의 attribute 에 담겨 있다!
			viewPage = "list.jsp";
			break;
			
		case "/write.do":
			viewPage = "write.jsp";
			break;
			
		case "/writeOk.do":
			command = new WriteCommand();
			command.execute(request, response);
			viewPage = "writeOk.jsp";
			break;
			
		case "/view.do":
			command = new ViewCommand();
			command.execute(request, response);
			viewPage = "view.jsp";
			break;
		case "/update.do":
			command = new SelectCommand();
			command.execute(request, response);
			viewPage = "update.jsp";
			break;
		case "/updateOk.do":
			command = new UpdateCommand();
			command.execute(request, response);
			viewPage = "updateOk.jsp";
			break;
		case "/deleteOk.do":
			command = new DeleteCommand();
			command.execute(request, response);
			viewPage = "deleteOk.jsp";
			break;
		} // end switch 
		
		
		
		
		// response 로서, 위 viewPage 를 forward 해줌.
		if(viewPage != null) {
			RequestDispatcher dispatcher =
					request.getRequestDispatcher("/WEB-INF/views/board/" + viewPage);
			// request 에 담긴 Command 수행결과도 viewPage 로 전달
			dispatcher.forward(request, response);
		}
		
	} // end doAction()
	
} // end Controller



- 이를 통해 MVC model2 에선 서블릿이 ‘컨트롤러’ 역할을 하고, 사용자는 컨트롤러를 통해서만 서버에 접근 가능하게 된다.


* request.setAttribute()   / getAttribute()

GET 방식의 request 이든 POST 방식의 request 이든 request 시 parameter가 담겨 가는 것은 잘 알고 있다.

그동안 request 에 담겨온 parameter를 받기 위해 request.getParameter( name ) 를 사용했었다.

그러나 getParameter 는 오직 문자열(String) 값밖에 받을 수 없었다.

반면에 request.setAttribute(name, value), request.getAttribute(name)어떠한 Object 타입도 보내고 받을 수 있다.  
getAttribute() 의 리턴타입은 Object 이니 필요한 타입으로 형변환 필요.

 

* List 구현 - ListCommand 작성
list.do 커맨드가 입력되면 처리할 커맨드 객체.
클래스 : ListCommand
패키지 : com.command.write
인터페이스 : com.command.write.Command

package com.command.write;

import java.sql.SQLException;
import java.util.List;

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

import com.lec.beans.WriteDAO;
import com.lec.beans.WriteDTO;

public class ListCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) {
		WriteDAO dao = new WriteDAO();

		List<WriteDTO> list = null;
		
		try {			
			// 트랜잭션 수행
			list = dao.select();
			
			// "list" 란 name 으로 request 에 list 를 담아 컨트롤러에 전달
			request.setAttribute("list", list);
		} catch(SQLException e) {
			e.printStackTrace();
			// ※ 예외 처리 필요 
			//  ex) 예외 페이지 설정, 혹은 예외 페이지로 redirect
		}
		
	}

}

ListCommand.java

“list” 란 name 으로 request 의  attiribute 에 list 값 담김 (정체는 DTO [] 배열)

 

* list.jsp

이전에는 JSP 에서 직접 DAO를 생성하여 사용했지만
이제는 request.getAttribute() 를 사용하여 컨트롤러를 통해 넘어오는 데이터를 받아서 출력한다.  

이 경우는 “list” 라는 name 값에 담긴 attribute 값을 받아온다.

JSP 에서 DAO가 없어진다. 이제 JSP 는 순수하게 ‘뷰’ 역할만 담당
‘뷰’ 역할 하는 JSP 는 ‘컨트롤러’ 에서 넘어오는 데이터를 받아서 화면에 그려주는 역할만 한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import = "com.lec.beans.*, java.util.*" %>
<% // Controller 로부터 결과 받음.
	List<WriteDTO> list = (List<WriteDTO>)request.getAttribute("list");
%>
<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>

    <title>목록</title>
</head>

<body>

    <div class="container mt-3">
        <h2>목록</h2>
        <hr>
        
        <table class="table table-hover">
            <thead class="table-success">
                <tr>
                    <th>#</th>
                    <th>제목</th>
                    <th>작성자</th>
                    <th>조회수</th>
                    <th>작성일</th>
                </tr>
            </thead>
            <tbody>
<% for(WriteDTO dto : list){ %>
                <tr>
                    <td><%= dto.getUid() %></td>
                    <td><a href="view.do?uid=<%= dto.getUid() %>"><%= dto.getSubject() %></a> </td>
                    <td><%= dto.getName() %></td>
                    <td><%= dto.getViewCnt() %></td>
                    <td><%= dto.getRegDateTime() %></td>
                </tr>
<% } %>
            </tbody>
        </table>
        <div class="row">
            <div class="col-12">
                <a class="btn btn-outline-dark" href="write.do">작성</a>
            </div>
        </div>
    </div>

</body>

</html>

MVC2 모델 하에서는 전적으로 Controller 가 Model 과 View 를 통제 하는 것
일반적으로 MVC2 모델에선 ‘뷰’ 파일을 직접적으로 request 할수 없도록 은닉 시키거나 차단 시킨다.


* 작성 구현 - WriteCommand 작성

패키지: com.command.write
클래스: WriteCommand
인터페이스 상속 : Command

package com.command.write;

import java.sql.SQLException;

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

import com.lec.beans.WriteDAO;
import com.lec.beans.WriteDTO;

public class WriteCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) {
		int cnt = 0;
		
		// 입력한 값 받아오기
		String name = request.getParameter("name");
		String subject = request.getParameter("subject");
		String content = request.getParameter("content");

		// ※ 파라미터 유효성 검증
		
		WriteDTO dto = new WriteDTO();
		dto.setName(name);
		dto.setSubject(subject);
		dto.setContent(content);
		
		WriteDAO dao = new WriteDAO();
		try {
			cnt = dao.insert(dto);	
		} catch (SQLException e) {
			e.printStackTrace();
		}

		request.setAttribute("result", cnt);
		request.setAttribute("dto", dto);  // auto-generated key (uid)
	}

}

* writeOk.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="com.lec.beans.*" %>
<%  // Controller 로부터 결과 데이터 받음
	int cnt = (Integer)request.getAttribute("result");
	WriteDTO dto = (WriteDTO)request.getAttribute("dto");
%>

<% if(cnt == 0){ %>
	<script>
		alert("등록 실패");
		history.back();
	</script>
<% } else { %>
	<script>
		alert("등록 성공");
		location.href = "view.do?uid=<%= dto.getUid() %>";
	</script>
<% } %>

 

MVC model 2 동작을 이해하자

- 어떻게 서블릿이 컨트롤러로서   모든 (*.do) request 를 받아들이는지?
- 어떻게 request 에서 command 를 분리해내고 분석하는지
- request 를 해당 command 에 넘기고 DB 트랜잭션 (비즈니스 로직) 실행하는지?
- 트랜잭션의 결과를 어떻게 다시 컨트롤러에게 넘겨 받는지?
- 컨트롤러는 그 결과를 어떻게 뷰 에 넘겨주는지?
동작순서를 이해해야 디버깅이 가능해진다.

 

*.jsp 를 숨겨라!
MVC 패턴에서는 비즈니스로직을 ‘컨트롤러’ 에서 처리하고 JSP 파일로 포워딩 한다.
뷰 (jsp 파일) 이 직접 url 을 통해 요청되는것은 막아야 한다.
어떻게?
방법1 :  WEB-INF 폴더 아래 두기 : ← 이 폴더는 보안상의 문제로 URL 로 직접 접근 못함.
방법2 : web.xml 에 세팅하기

 

명심)
FrontController가  모든 request 를 받는 컨트롤러 ( Controller )다.
컨트롤러는 request 를 분석하여 어떠한 Command를 수행할지 결정
Command 결과에 따라 어떠한 View 로 넘길지 결정

‘설계’가 관건이다.
페이지 를 구현하기 위해 어떠한 DAO(들)이 필요하며,  각 DAO 에는 어떠한 동작을 하는 메소드들을 설계할것인가?
어떠한 Coommand 들로 DAO 객체를 사용하고, 어떠한 parameter 들을 넘기고, 어떠한 attribute 값들을 주고 받을것인가?