목차

sun cloud


Web Architecture

: 웹 아키텍쳐로는 전에 3 tier architecture에 대해 간단히 배우고 넘어간 적이 있습니다. 자세히 다루지는 않았었는데 이번에 배우는 Model2 Architecture는 상당히 중요한 개념으로 자세히 짚고 넘어갈 예정입니다.

우선 아키텍쳐가 적용되지 않은 방식부터 차차 연습해서 MVC 아키텍쳐의 장점을 느낄 수 있도록 차근차근 진행하겠습니다.



초기 웹 개발 방식

: 단순하고 직관적인 구조를 가지고 있습니다. 하지만 변수가 다양할수록 작업량이 늘어나서 생산성이 떨어지고 응집도도 높아서 유지 보수에 어려움이 있다는 단점이 있습니다.

기본 구조는 다음과 같이 JSP와 DB간의 연동만으로 이루어집니다.

JSP <───> Database


간단하게 회원수를 가져오는 예제로 연습해보겠습니다. 기존에 만들었던 테이블을 import해서 사용할텐데 중요한 데이터는 아니기에 자세한 설명은 생략하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" import="java.sql.*"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jsp와 db연동</title>
</head>
<body>
	<%
	String driver = "oracle.jdbc.OracleDriver";
	Class.forName(driver);
	String url="jdbc:oracle:thin:@127.0.0.1:1521:xe";
	Connection con=DriverManager.getConnection(url,username,password);
	String sql="select count(*) from member";
	PreparedStatement pstmt=con.prepareStatement(sql);
	ResultSet rs=pstmt.executeQuery();
	int count = 0;
	if(rs.next()){
	    count=rs.getInt(1);
	}
	rs.close();
	pstmt.close();
	con.close();
	%>
	<h3>초기 웹 개발 방식: jsp와 database 연동</h3>
	<h4>총 회원수 <%=count%></h4>
</body>
</html>

DB에 있는 회원수 데이터를 조회하기 위해서는 우선 DB와 연동을 해야하겠죠?
연결을 한 뒤에는 SQL문으로 데이터를 가져오고, 현재 페이지에서 데이터를 가져왔기에 바로 할당한 데이터를 출력할 수 있습니다.


매우 간단하고 직관적인 방식이지만, 단순하고 아주 간단한 작업을 했음에도 한 페이지에서 작성한 코드양이 적지 않은 것 같습니다. 데이터나 필요한 작업이 많아질수록 유지보수가 어려워지는 것은 피할 수 없을 것 같습니다.



Model1 Architecture

기존에 JSP가 하던 일을 Java Beans와 분담하는 구조입니다.

model1

참고) Software Architecture: 소프트웨어 구성 요소들 사이의 유기적 관계를 표현하며 소프트웨어 설계와 업그레이드를 통제하는 지침과 원칙입니다

JSP<───> Java Beans(or Component) <───> Database

참고: bean - 커피 콩, 알맹이. 컴포넌트를 지칭하며 다양하게 사용되고 있습니다. i.e) spring bean, BeanFactory

참고: component란 객체들이 상호 연동되어 독립적 기능 단위를 구성할 때 컴포넌트라고 합니다. 재사용 가능한 독립적 기능 모듈을 지칭하며 소프트웨어 구성 요소입니다.

JSP: 클라이언트의 요청을 분석하고 Java Beans와 연동해 적절한 결과를 클라이언트에게 전달합니다. Java Beans: Java Class(or Object)들로 구성된 컴포넌트를 말하며 DB 연동 로직과 Business Logic을 수행합니다.




Model1 Architecture 예제

DB와 연동하여 모든 멤버의 수를 출력하는 페이지와 아이디를 통해 해당 멤버를 찾는 페이지를 만들어보겠습니다.

총 세 개의 페이지가 만들어집니다.

index.jsp -> AllMemberCount.jsp, findMemberById-form

첫 화면부터 확인해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Model1</title>
</head>
<body>
	<h4>Model 1 Architecture 테스트</h4>
	<%-- 이전 방식(jsp-db.jsp)과 비교해본다 --%>
	<ul>
		<li><a href="allMemberCount.jsp">총회원수 조회</a></li>
		<li><a href="findMemberById-form.jsp">아이디로 회원검색</a></li>
	</ul>
	<img src="model1.jpg">
</body>
</html>

이전 방식과 비교해 볼 수 있도록 진행하려 합니다.

첫 페이지에서는 회원수 조회 페이지와 회원검색 페이지로 이동할 수 있으며 model1 아키텍쳐의 이해를 위해 사진도 첨부했습니다.

다음으로 DB와의 연동을 위해 MemberDAO클래스를 src/main/java폴더에서 model패키지에 만들어야 합니다. 회원 검색 기능에서는 검색 결과에 따른 회원 정보를 반환하기 때문에 MemberVO클래스 역시 model패키지에 작성하도록 합니다.


MemberDAO클래스에서 모든 메서드들을 구현합니다. 그래서 우선 jsp 페이지부터 작성하도록 하겠습니다.(예제를 진행할 때에는 페이지에 필요한 기능을 DAO에서 작성하고 jsp를 작성하는 순서로 진행했습니다)

allMemberCount.jsp 페이지를 작성하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@page import="model.MemberDAO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Model1 Architecture 회원수 조회</title>
</head>
<body>
    <%-- Model1 Architecture 회원수 조회 --%>
    <%
    MemberDAO dao=new MemberDAO();
    int count=dao.getTotalCount();
    %>
	<h4>총 회원수 <%=count%></h4>
</body>
</html>

MemberDAO 클래스를 사용하기 위해 첫번째 라인과 같이 import 작업이 필요합니다.

그리고 MemberDAO 인스턴스를 생성해서 참조변수를 통해 getTotalCount()메서드를 사용합니다.



다음으로 회원 검색 페이지인 findMemberById-form.jspfindMemberById-action.jsp 페이지를 작성해보겠습니다.

검색 기능은 사용자가 id를 입력하는 페이지와 해당 id와 일치하는 아이디, 이름, 주소 정보를 출력하는 페이지 총 2개의 jsp 파일이 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원검색폼</title>
</head>
<body>
<%-- Model1 Architecture 방식으로 회원 검색 서비스 구현 --%>
<form action="findMemberById-action.jsp">
<input type="text" name="memberId" placeholder="아이디" required="required">
<button type="submit">검색</button>
</form>
</body>
</html>

위 코드를 통해 회원 아이디를 입력하는 폼을 작성했고, 여기서 전송한 정보를 출력하는 페이지를 만들어보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" import="model.MemberVO" import="model.MemberDAO"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>findMemberById-action.jsp</title>
</head>
<body>
	<%
	String id=request.getParameter("memberId");
	MemberDAO dao=new MemberDAO();
	MemberVO vo = dao.findMemberById(id);
	if(vo!=null){
	%>
	아이디: <%=vo.getId()%><br>
	이름: <%=vo.getName()%><br>
	주소: <%=vo.getAddress()%>
	<%}else{%>
	
    <script type="text/javascript">
	alert("<%=id%> 아이디에 대한 회원 정보가 없습니다!");
	location.href="findMemberById-form.jsp";
	</script>
	<%} %>
    
</body>
</html>

조회 기능과 마찬가지로 필요한 클래스들을 import해야 아래 작성할 MemberDAOMemberVO클래스의 사용이 가능합니다.

바디 태그를 보시면 전달받은 idgetParameter() 메서드를 통해 저장하고, MemberDAO클래스에서 작성한 findMemberById()메서드의 매개변수로 사용합니다.

이 외의 내용들은 이전 수업들에서 다뤘던 내용이기에 설명을 생략하겠습니다.

이제 기능을 담은 MemberDAO클래스를 확인하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class MemberDAO {
    private String Driver = "oracle.jdbc.OracleDriver";
    private String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
    private String username = "scott";
    private String password = "tiger";

    public MemberDAO() throws ClassNotFoundException {
        Class.forName(Driver);
    }

    public void closeAll(ResultSet rs, PreparedStatement pstmt, Connection con) throws SQLException {
        if (rs != null)
            rs.close();
        closeAll(pstmt, con);
    }

    public void closeAll(PreparedStatement pstmt, Connection con) throws SQLException {
        if (pstmt != null)
            pstmt.close();
        if (con != null)
            con.close();
    }

    public int getTotalCount() throws SQLException {
        int count = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            con = DriverManager.getConnection(url, username, password);
            String sql = "select count(*) from member";
            pstmt = con.prepareStatement(sql);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                count = rs.getInt(1);
            }
        } finally {
            closeAll(rs, pstmt, con);
        }
        return count;
    }
    
    public MemberVO findMemberById(String id) throws SQLException {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        MemberVO vo=null;
        try {
            con = DriverManager.getConnection(url, username, password);
            String sql = "select name, address from member where id=?";
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, id);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                vo = new MemberVO(id,rs.getString(1),rs.getString(2));
            }
        } finally {
            closeAll(rs, pstmt, con);
        }
        return vo;
    }
}

코드 전반적으로 DB 수업에서 다뤘던 내용들입니다.


다음은 MemberVO클래스입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package model;

public class MemberVO {
    private String id;
    private String password;
    private String name;
    private String address;

    public MemberVO() {
        super();
    }

    public MemberVO(String id, String password, String name, String address) {
        super();
        this.id = id;
        this.password = password;
        this.name = name;
        this.address = address;
    }

    public String getId() {
        return id;
    }

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

    public String getPassword() {
        return password;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}


실행 결과는 다음과 같습니다.

model1




Model2 Architecture

Model 2 Architecture MVC Design Pattern에 대해 알아보겠습니다.

Model 2 설계방식의 근간을 이루는 Design Pattern으로 흔히 Model2 or MVC or Web MVC or Model2 MVC와 같이 다양하게 불리고 있습니다.


Model2 MVC web application 설계 방식으로 Model과 View, 그리고 Controller 영역으로 분리해서 설계 구현하는 것을 말합니다.
Model Java Beans(or Java Component)로 비즈니스 로직데이터 액세스 로직을 정의합니다.
- DAO, Service, VO, DTO 등이 Model에 속합니다.
View 클라이언트에게 동적인 웹 페이지를 제공하는 역할로 JSP가 담당합니다.
Controller 웹 어플리케이션의 제어자 역할로써 클라이언트의 요청을 분석하며 Model과 연동하거나 적절한 이동방식(forward or redirect)으로 View를 선택해 클라이언트에게 응답하게 합니다.
- Servlet이 담당합니다.


아래와 같은 구조를 가집니다.

image



다음 그림은 물리적인 구조를 보여주는 Model2 Architecture의 Deployment diagram입니다.

Model2-1

같은 구조를 순차적 관계로 보여주는 Sequence diagram입니다.

Model2-2




forward, redirect

  • Controller(Servlet)에서 View(JSP)로 이동하는 방식으로 forwardredirect방식이 있습니다.
  1. forward 방식

    • requestresponse가 유지되면서 이동되는 방식입니다.
    • 브라우저는 이동한 페이지의 url을 알 수 없습니다. WAS(Web Container)에서 이동(= 서버 상에서 이동)하기 때문에 클라이언트 측은 이동 여부를 모릅니다.
    • 클라이언트가 재요청을 할 경우 기존 동작이 반복됩니다. 필요 시 Model과의 연동 결과를 request 객체에 할당(request.setAttribute(name,value))하고 View에서 해당 정보를 이용하여 클라이언트에게 응답(request.getAttribute(name))하는 방식입니다.

    ex) request.getRequestDispatcher(view url).forward(request,response);

  2. redirect 방식

    • 기존 requestresponse가 유지되지 않습니다.
    • 재요청 시 기존 동작을 반복하지 않습니다.
    • 이동 시 client에게 url을 전달해서 client가 다시 이동하게 하는 방식으로 다시 이동할 때 새로운 request와 response가 생성됩니다.

    ex) response.sendRedirect(view url);




Model2 Architecture 예제

forward 방식의 예제를 진행하겠습니다.

앞에서 사용했던 MemberVO와 MemberDAO를 사용합니다.

첫 화면인 index.jsp 화면부터 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Model2 MVC</title>
</head>
<body>
<h3>Model2 MVC</h3>
<ul>
    <li><a href="AllMemberCountServlet">총회원수 조회</a></li>
    <li><a href="findMemberById-form.jsp">아이디로 회원검색</a></li>
</ul>
<img src="Model2-1.png">
<img src="forward-redirect.png">
</body>
</html>

MVC Model2 예제 역시 model 1에서 했던 것처럼 총회원수 조회 페이지와 회원 검색 페이지를 만들도록 하겠습니다.

만들어야 할 파일 구조는 다음과 같습니다.

image

파일은 많지만 코드 내용은 단순하기 때문에 자세한 설명은 생략하도록 하겠습니다. 중요한 것은 위와 같이 구조를 Model, View, Controller로 나누어 구현한다는 점 입니다. 코드는 깃허브 링크를 통해 확인하실 수 있습니다.




forward, redirect 예제

마지막으로 forwardredirect 예제입니다. 이동방식에 따른 차이점을 알아보는 예제이기에 코드의 내용은 간단하고 단순하게 작성했습니다.

구조는 다음과 같습니다.

image


우선 테스트를 위한 모조 객체 DAO를 만들겠습니다.

1
2
3
4
5
6
7
package model;

public class MockDAO {
    public void register(String info) {
        System.out.println("MockDAO register 메서드에서 데이터베이스에 정보를 등록: "+info);
    }
}


다음으로 index.jsp입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>forward redirect 이동방식</title>
</head>
<body>
<a href="ForwardTestServlet">forward 이동방식 테스트</a><br><br>
<a href="RedirectTestServlet">redirect 이동방식 테스트</a><br><br>
<img src="forward-redirect.png">
</body>
</html>


Controller 역할의 ForwartTestServlet입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package controller;

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 model.MockDAO;

/**
 * Servlet implementation class ForwardTestServlet
 */
@WebServlet("/ForwardTestServlet")
public class ForwardTestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	    //Model과 연동
	    MockDAO dao=new MockDAO();
	    dao.register("상품정보");
	    //forward 방식으로 이동
	    String path="forward-result.jsp";
	    request.getRequestDispatcher(path).forward(request, response);
	}
}


RedirectTestServlet입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package controller;

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 model.MockDAO;

/**
 * Servlet implementation class RedirectTestServlet
 */
@WebServlet("/RedirectTestServlet")
public class RedirectTestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	    //모델 연동
	    MockDAO dao=new MockDAO();
	    dao.register("상품정보");
	    //redirect 방식으로 이동
	    request.setAttribute("shareInfo", "검색정보");
	    response.sendRedirect("redirect-result.jsp");
	}
}

redirect 방식은 기존 request와 response는 유지되지 않기에 24번 라인에서 할당한 shareInfo는 공유할 수 없습니다. 확인을 위해 작성했습니다.


결과를 확인할 forward-result.jsp입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>forward-result.jsp</title>
</head>
<body bgcolor="yellow">
forward-result view page
<%-- forward 방식이므로 request에 저장한 정보를 이용할 수 있다 --%>
<%=request.getAttribute("shareInfo")%>
</body>
</html>


redirect-result.jsp입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>redirect-result.jsp</title>
</head>
<body bgcolor="orange">
redirect-result view page
<%-- redirect 방식은 아래와 같이 request에 저장된 정보를 사용할 수 없다 --%>
<%= request.getAttribute("shareInfo") %><%-- null 출력 --%>
</body>
</html>



실행 결과는 다음과 같습니다.

forward redirect

또한 forward 방식의 경우 새로고침을 하면 다음과 같이 작업을 재실행하는 것을 확인할 수 있습니다.

image



두 이동 방식은 특정 방법이 좋은 것이 아닌 상황에 맞는 활용이 필요합니다.

forward 이동방식은 새로고침을 해도 문제가 없고, request에 저장한 정보를 이용해야 할 경우 사용합니다. 보통 정보 조회에 많이 사용되곤 합니다.

반면 redirect는 재동작 되어서는 안되는 작업에 사용되어 보통 로그인, 로그아웃 등의 기능을 요청할 때 사용합니다.



이것으로 오늘 복습을 마치도록 하겠습니다.



맨 위로 이동하기

Java Web Programming 카테고리 내 다른 글 보러가기

댓글남기기