Loading...

Web / / 2022. 2. 26. 15:50

Web 18강. MVC 패턴 사용

반응형

MVC 패턴에서는 redirection을 사용하지 못한다.

 

정적인 파일인 login 페이지를 요청하면

redirection으로 뷰(login.jsp)로 바로 찾아갈 수 있다.

 

만약 동적인 데이터가 필요하면 데이터베이스를 찾아가야하는데,

이를 Model이라고 한다.

 

동적인 페이지가 필요한 요청이 오면 MVC 패턴,

정적인 파일을 요청하면 CV패턴 -> 프론트 컨트롤러 패턴을 사용한다.

 

모델이 필요 없기 때문에 redirection으로 해결할 수 있다.

 

모델이 필요한 요청이 오면

어떻게 데이터를 넘겨줄 수 있을까?

 

request 공간에 데이터가 저장되어 있다면

request.getAttribute( )를 호출하여

데이터를 가지고 있을 것이다.

 

그런데 redirection을 함으로써

FC는 응답을 마치고 새로 요청을 하면서

request 공간은 사라지고

새로운 request 공간이 생겨서

저장되어 있던 데이터는 날아간다.

 

이때는 간단하다.

원래 request 공간을 새로운 request 공간에

끌고 가서 덮어 씌워버리면 된다.

 

이렇게 사용하면 브라우저는 redirection 되었는지 모르고,

302 코드도 뜨지 않는다.

 

http 헤더까지 다 같이 복제되기 때문이다.

Servlet ↔ Controller ↔ Repository ↔ Database

이런 모양으로 의존 관계를 가진다.

 

의존 관계가 많아지면

불편하고 복잡해지기 때문에

MVC 패턴이 나왔다.

 


 

 

server.xml을 보면

docBase가 서버가 관리 중인 프로젝트를 말한다.

Context 문을 지워버리는 것은

서버 관리할 때 왼쪽으로 Remove 하는 것과 같은 행위이다.

 


아래 사진과 같이

패키지와 클래스를 생성해준다.

 

domain 패키지에 만든 파일 2개가

MVC 중 M(Model)이다.

 

이 전체 틀은 고정적이라고 생각하자.

 

더 쉬운 방법을 찾아 내가 더 효율적으로 바꿀 수 있겠지만

그렇게 되면 협업이 어려워진다.

 


 

동적인 페이지를 만들기 위해 연동시킬

테이블을 생성해준다.

CREATE TABLE myUser (
  id number primary key,
  username varchar2(100),
  password varchar2(100),
  email varchar2(100)
);

CREATE SEQUENCE SEQ_MYUSER INCREMENT BY 1 START WITH 1;

INSERT INTO myUser(id, username, password, email) VALUES(seq_myUser.nextval, 'ssar', '1234', 'ssar@nate.com');
INSERT INTO myUser(id, username, password, email) VALUES(seq_myUser.nextval, 'cos', '1234', 'cos@nate.com');
INSERT INTO myUser(id, username, password, email) VALUES(seq_myUser.nextval, 'love', '1234', 'love@nate.com');
commit;

SELECT * FROM myUser;

 


데이터베이스의 myUser 테이블을

자바 세상에 모델링한다.

package site.metacoding.mvc.domain;

// 데이터베이스의 테이블을 자바 세상에 모델링
public class MyUser {
	private int id;
	private String username;
	private String password;
	private String email;

	// 기본 생성자
	public MyUser() {

	}

	// 풀생성자
	public MyUser(int id, String username, String password, String email) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
		this.email = email;
	}

	// id
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	// username
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	// password
	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	// email
	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

 


 

아래 코드는 DB와 연결하기 위한 코드가 아닌

DBCP를 이용해 이미 만들어져 있는 커넥션 객체를

받아오기 위한 메서드이다.

package site.metacoding.mvc.config;

import java.sql.Connection;

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

public class DBConn {

	// 이미 만들어져있는 커넥션 객체를 리턴받기 위한 메서드, DB 커넥션 코드 아님!!
	// 메서드의 책임 : Connection 객체 return
	public static Connection getConnection() {
		// 목적을 위한 객체는 전역적으로 선언
		Connection conn = null;

		try {
			Context initContext = new InitialContext();
			Context envContext = (Context) initContext.lookup("java:/comp/env"); // JNDI 박스, env는 외부환경
			DataSource ds = (DataSource) envContext.lookup("jdbc/myoracle");
			conn = ds.getConnection();
			System.out.println("DB연결 성공");
		} catch (Exception e) {
			System.out.println("DB연결 실패");
			e.printStackTrace();
		}

		return conn;
	}
}

 


 

DispatcherServlet은 어떤 FrontController를 찾아줄지

결정해주는 역할을 한다.

 

디스패쳐 서블릿에서 도메인을 확인하여

맞는 컨트롤러로 연결해준다.

 

어떤 요청이 오든 디스패쳐 서블릿으로 들어와야 하기 때문에

어노테이션을 사용해 context path를 /로 지정해준다.

package site.metacoding.mvc;

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 site.metacoding.mvc.web.MyUserController;

// http://localhost:8000/
// http://localhost:8000/hello
// 슬래쉬만 적어놓으면 뒤에 뭐가오든 이쪽으로 들어옴

// http://localhost:8000/myuser가 들어오면 유저 컨트롤러로 가게 파싱 해줘야함
// http://localhost:8000/mypost가 들어오면 포스트 컨트롤러로 가게 파싱 해줘야함

@WebServlet("/") // 어떤 요청이 들어와도 이 서블릿이 받음
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String domain = request.getRequestURI();

		System.out.println("domain : " + domain);

		if (domain.equals("/myuser")) {
			MyUserController controller = new MyUserController(request, response);

			// int id = Integer.parseInt(request.getParameter("id"));
			String idStr = request.getParameter("id"); // null

			if (idStr == null) {
				controller.getAll();
			} else {
				int id = Integer.parseInt(idStr);
				controller.getOne(id);
			}
		}
	} // Dispatcher 스레드 종료 (1. 클라이언트에게 연결된 response선 끊김 -> stateless, 2. request 메모리
		// 영역 삭제, 3. DB Connection 반납)

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

	} // Dispatcher 스레드 종료

}

 

http://localhost:8000/myuser라는 get 요청이 들어왔다.

도메인은 http://localhost:8000 뒤에 붙은

/myuser를 말한다.

이를 기준으로 myuser가 들어오면 user 컨트롤러로 보내줘야 한다.

 

user 컨트롤러에서는 2가지 일을 한다.

모든 회원의 정보를 출력하는 getAll( ),

원하는 id의 회원 정보를 출력하는 getOne( )이다.

 

두 가지를 구별하는 기준은 쿼리 스트링의 유무이다.

 

request의 getParameter 메서드를 이용해

id의 값이 있으면 getOne( ),

쿼리 스트링이 없어서 id 값이 null이면 getAll( )을 호출한다.

 

if (domain.equals("/myuser")) {
	MyUserController controller = new MyUserController(request, response);

	// int id = Integer.parseInt(request.getParameter("id"));
	String idStr = request.getParameter("id"); // null

	if (idStr == null) {
		controller.getAll();
	} else {
		int id = Integer.parseInt(idStr);
		controller.getOne(id);
	}
}

 


 

user 컨트롤러는 회원 관련(myUser 테이블) 요청만 컨트롤한다.

package site.metacoding.mvc.web;

import java.util.List;

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

import site.metacoding.mvc.domain.MyUser;
import site.metacoding.mvc.domain.MyUserRepository;

// 회원 관련(myUser 테이블) 요청만 컨트롤함
public class MyUserController {

	private HttpServletRequest request;
	private HttpServletResponse response;
	private MyUserRepository repo;

	public MyUserController(HttpServletRequest request, HttpServletResponse response) {
		this.request = request;
		this.response = response;
		repo = new MyUserRepository();
	}

	// GET:http://localhost:8000/myuser
	public void getAll() {
		System.out.println("getAll 호출됨");

		List<MyUser> myUserList = repo.selectAll();
		System.out.println("myUserList : " + myUserList);

		request.setAttribute("myUserList", myUserList);
		try {
			RequestDispatcher dis = request.getRequestDispatcher("/user.jsp");
			dis.forward(request, response);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// GET:http://localhost:8000/myuser?id=1
	public void getOne(int id) {
		System.out.println("getOne 호출됨");

		MyUser myUser = repo.selectById(id);
		System.out.println("myUser : " + myUser.getUsername());

		// 무슨타입으로 받을지 모르기때문에 오브젝트 타입으로 받고 있음 -> 실제로 사용할때는 다운캐스팅 해줘야 함
		request.setAttribute("myUser", myUser); // 키, 밸류
		try {
			// response.sendRedirect("/userInfo.jsp"); // 저장장치가 Redirect 수행
			RequestDispatcher dis = request.getRequestDispatcher("/userInfo.jsp");
			dis.forward(request, response);
		} catch (Exception e) {
			// userInfo.jsp가 없을수도 있으니까
			e.printStackTrace();
		}
	}
}

 

sendRedirect

원래 Redirection을 하면

기존의 request가 가지고 있던 데이터가

새로 request 공간이 생길 때 사라졌었다.

 

RequestDispatcher

 

여기서 request를 복제시키는 방법이 나온다.

RequestDispatcher 객체를 사용하는 것이다.

getRequestDispatcher는 redirection 할 파일의 경로를 적어주고

dis.forward를 통해 덮어 씌우는 것이다.

 

 


 

package site.metacoding.mvc.domain;

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

import site.metacoding.mvc.config.DBConn;

// INSERT, DELETE, UPDATE, SELECT하는 메서드 미리 만들어서 재사용
// 5가지 메서드 꼭 필요한 필수 메서드
public class MyUserRepository {

	private Connection conn;

	public MyUserRepository() {
		conn = DBConn.getConnection();
	}

	// SELECT * FROM myUser;
	public List<MyUser> selectAll() {
		List<MyUser> myUsers = new ArrayList<>();
		MyUser myUser = null;

		try {
			String sql = "SELECT * FROM myUser";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			ResultSet rs = pstmt.executeQuery();

			while (rs.next()) {

				myUser = new MyUser(rs.getInt("id"), rs.getString("username"), rs.getString("password"),
						rs.getString("email"));
				myUsers.add(myUser);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("UserSize : " + myUsers.size());
		return myUsers;
	}

	// SELECT * FROM myUser WHERE id = ?
	public MyUser selectById(int id) {
		MyUser myUser = null;

		try {
			String sql = "SELECT * FROM myUser WHERE id = ?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			ResultSet rs = pstmt.executeQuery();

			if (rs.next()) { // 결과가 한 건 밖에 없어서 굳이 while 돌릴 필요 없다.
				myUser = new MyUser(rs.getInt("id"), rs.getString("username"), rs.getString("password"),
						rs.getString("email"));
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return myUser;
	}

	// INSERT INTO myUser(id, username, password, email)
	// VALUES(SEQ_myUser.nextVal,?,?,?)
	public int insert(String username, String password, String email) {
		int result = 0;

		return result;
	}

	// DELETE FROM myUser WHERE id = ?
	public int deleteById(int id) {
		int result = 0;

		try {
			String sql = "DELETE FROM myUser WHERE id = ?";
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			result = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// UPDATE myUser SET username = ?, password = ?, email = ? WHERE id = ?
	public int update(int id, String username, String password, String emial) { // MyUser myUser를 받아와도 됨
		int result = 0;

		return result;
	}
}

 


 

<%@page import="site.metacoding.mvc.domain.MyUser"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>회원정보 페이지 입니다.</h1>
<hr/>
<%
	// import 하려면 무조건 자동완성으로 ㅜㅜ
	// 다운캐스팅 해줘야 함!!
	MyUser myUser = (MyUser) request.getAttribute("myUser");
%>
아이디 : <%=myUser.getId() %> <br/>
유저네임 : <%=myUser.getUsername() %> <br/>
패스워드 : <%=myUser.getPassword() %> <br/>
이메일 : <%=myUser.getEmail() %> <br/>
</body>
</html>

 

<%@page import="site.metacoding.mvc.domain.MyUser"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>전체 회원정보 페이지 입니다.</h1>
<hr/>
<%
List<MyUser> myUserList = (List<MyUser>) request.getAttribute("myUserList");

for(int i = 0; i < myUserList.size(); i++) { %>
아이디 : <%=myUserList.get(i).getId() %> <br/>
유저네임 : <%=myUserList.get(i).getUsername() %> <br/>
패스워드 : <%=myUserList.get(i).getPassword() %> <br/>
이메일 : <%=myUserList.get(i).getEmail() %> <br/>
<hr/>
<%} %>
</body>
</html>

 

 

 

 

 

 

 

 

 

[출처]

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com


메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

반응형