MVC 디자인 패턴: Model-View-Controller의 역할과 상호작용
MVC (Model-View-Controller) 디자인 패턴은 웹 애플리케이션의 구조를 명확하게 나누어 각 부분의 책임을 분리하는 패턴입니다. 이 패턴을 통해 코드의 유지보수성, 재사용성, 확장성을 높일 수 있습니다. 각 구성 요소의 역할과 그 상호작용을 더 자세히 살펴보겠습니다.
1. Model (모델)
- 역할: 애플리케이션의 데이터와 비즈니스 로직을 처리합니다. 데이터의 상태를 표현하고, 데이터와 관련된 모든 작업을 수행합니다. 모델은 애플리케이션의 핵심 비즈니스 로직을 포함하며, 데이터베이스와의 상호작용을 처리합니다.
- 구성:
- DAO (Data Access Object): 데이터베이스와의 직접적인 상호작용을 담당합니다. SQL 쿼리를 실행하여 데이터를 조회하거나 수정합니다. DAO는 데이터베이스와의 연결을 관리하며, 데이터의 저장과 조회를 담당합니다.
- Service (서비스): 비즈니스 로직을 처리합니다. DAO를 호출하여 데이터베이스에서 정보를 가져오거나 저장하며, 그 결과를 Controller에 반환합니다. 서비스 계층은 애플리케이션의 비즈니스 규칙을 적용하고, 데이터의 처리와 검증을 수행합니다.
- 상호작용:
- Service는 DAO를 호출하여 데이터베이스 작업을 수행합니다.
- 모델은 Controller로부터의 요청을 처리하고, 결과를 Controller로 반환합니다.
2. View (뷰)
- 역할: 사용자에게 정보를 시각적으로 표현하는 역할을 합니다. 사용자 인터페이스를 구성하며, 데이터의 표시 및 입력 양식을 제공합니다. 뷰는 주로 HTML, CSS, JavaScript로 작성되어 사용자에게 최종적으로 보여지는 페이지를 생성합니다.
- 구성:
- JSP (JavaServer Pages): 동적인 웹 페이지를 생성하는 데 사용됩니다. 서버 측에서 Java 코드와 HTML을 혼합하여 동적인 콘텐츠를 생성할 수 있습니다. JSP는 데이터베이스에서 가져온 정보를 사용자에게 표시하는 역할을 합니다.
- 상호작용:
- Controller가 모델에서 받은 데이터를 View에 전달하여 최종적으로 사용자에게 보여주는 페이지를 생성합니다.
- View는 사용자와 상호작용하며, 사용자 입력을 Controller에 전달합니다.
3. Controller (컨트롤러)
- 역할: 사용자 요청을 받아 적절한 모델을 호출하고, 모델의 결과를 바탕으로 적절한 뷰를 선택하여 사용자에게 응답을 전달합니다. Controller는 애플리케이션의 흐름을 제어하며, 사용자 입력을 처리합니다.
- 구성:
- 서블릿 (Servlet) 또는 스프링 MVC Controller: 사용자 요청을 처리하고, 서비스 메서드를 호출하며, 결과를 View에 전달합니다.
- 상호작용:
- 사용자로부터의 요청을 받아 적절한 Service 메서드를 호출합니다.
- Service로부터 받은 데이터를 기반으로 적절한 JSP 페이지를 선택하고, 결과를 사용자에게 반환합니다.
MVC 패턴의 상호작용 흐름
- 사용자 요청: 사용자가 웹 애플리케이션에서 요청을 보냅니다 (예: 특정 URL에 접근, 폼 제출).
- Controller 처리: Controller는 요청을 수신하고, 해당 요청을 처리하기 위해 적절한 Service 메서드를 호출합니다.
- Service 작업: Service는 비즈니스 로직을 수행하고 필요한 데이터 작업을 위해 DAO를 호출합니다.
- DAO 데이터 처리: DAO는 데이터베이스와 상호작용하여 필요한 데이터를 조회하거나 업데이트합니다.
- Service 결과 반환: Service는 DAO의 결과를 처리하고, Controller로 반환합니다.
- View 렌더링: Controller는 결과를 바탕으로 적절한 JSP 페이지를 선택하고, 사용자에게 최종적으로 렌더링된 페이지를 제공합니다.
MVC의 장점
- 유지보수성: 각 계층이 독립적으로 동작하므로, 특정 계층의 변경이 다른 계층에 영향을 미치지 않습니다. 예를 들어, 비즈니스 로직이 변경되면 Service 계층만 수정하면 되고, UI 변경이 필요하면 View 계층만 수정하면 됩니다.
- 재사용성: 비즈니스 로직이 Service 계층에 모여 있어, 다양한 View에서 재사용할 수 있습니다. 데이터 접근 코드가 DAO에 있어, 다른 Service에서도 재사용할 수 있습니다.
- 테스트 용이성: 각 계층을 독립적으로 테스트할 수 있어, 비즈니스 로직이나 데이터 접근 로직을 개별적으로 검증할 수 있습니다.
- 확장성: 새로운 기능을 추가하거나 변경할 때, 기존 코드에 미치는 영향을 최소화할 수 있습니다. 새로운 View나 Service를 추가하는 것이 용이합니다.
MVC 패턴은 애플리케이션을 더 잘 조직화하고, 각 구성 요소의 역할과 책임을 명확히 하여 복잡한 애플리케이션을 관리하는 데 매우 유용한 디자인 패턴입니다.
MVC 모델 기반으로 코드 작성
1. Model (모델)
Model은 애플리케이션의 데이터와 비즈니스 로직을 처리합니다. 여기서는 RoomDaoImpl과 RoomServiceImpl이 모델의 역할을 수행합니다.
RoomDaoImpl
- 역할: 데이터베이스와 상호작용하여 방 데이터의 CRUD 작업을 처리합니다.
- 주요 메서드:
- findAllRoomListItems(int offset, int pageSize): 페이징 처리를 적용하여 방 목록을 조회합니다.
- getTotalRoomCount(): 데이터베이스에서 전체 방의 수를 계산하여 반환합니다.
package com.mywebapp.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.mywebapp.dto.RoomListItemDto;
import com.mywebapp.model.Room;
import com.mywebapp.util.JdbcUtil;
public class RoomDaoImpl implements RoomDao {
@Override
public long insert(Room room) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
long roomId = -1;
String sql = "INSERT INTO room (host_id, room_name, jibun_address, street_address, address_detail, floor, usable_area, room_count, " +
"living_room_count, toilet_count, kitchen_count, duplex, elevator, park, park_detail, room_type, " +
"minimum_contract, approve) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
try {
con = JdbcUtil.getCon();
pstmt = con.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
pstmt.setString(1, room.getHostId());
pstmt.setString(2, room.getRoomName());
pstmt.setString(3, room.getJibunAddress());
pstmt.setString(4, room.getStreetAddress());
pstmt.setString(5, room.getAddressDetail());
pstmt.setInt(6, room.getFloor());
pstmt.setInt(7, room.getUsableArea());
pstmt.setInt(8, room.getRoomCount());
pstmt.setInt(9, room.getLivingRoomCount());
pstmt.setInt(10, room.getToiletCount());
pstmt.setInt(11, room.getKitchenCount());
pstmt.setBoolean(12, room.isDuplex());
pstmt.setBoolean(13, room.isElevator());
pstmt.setBoolean(14, room.isPark());
pstmt.setString(15, room.getParkDetail());
pstmt.setInt(16, room.getRoomType());
pstmt.setInt(17, room.getMinimumContract());
pstmt.setInt(18, room.getApprove());
int affectedRow = pstmt.executeUpdate();
if (affectedRow == 0) {
throw new SQLException("Insert failed");
}
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
roomId = rs.getLong(1);
System.out.println(roomId);
}else {
throw new SQLException("NO ID");
}
}catch (SQLException e){
e.printStackTrace();
return -1;
}finally {
JdbcUtil.close(con, pstmt, null);
}
return roomId;
}
/* 게스트 페이지에서 보여줄 방 리스트 + 페이징 처리*/
@Override
public List<RoomListItemDto> findAllRoomListItems(int offset, int pageSize) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
// LIMIT은 페이지 크기(한 페이지에 보여줄 데이터의 수)
// OFFSET은 데이터베이스에서 시작할 위치 (OFFSET은 0부터 시작)
String sql = "SELECT ri.image_path, ri.image_name, r.room_name, r.street_address, rp.rent_price, ro.room_option " +
"FROM room r " +
"INNER JOIN room_image ri ON r.id = ri.room_id " +
"INNER JOIN room_option ro ON r.id = ro.room_id " +
"INNER JOIN room_price rp ON r.id = rp.room_id " +
"LIMIT ? OFFSET ?";
try {
con = JdbcUtil.getCon();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, pageSize);
pstmt.setInt(2, offset);
rs = pstmt.executeQuery();
List<RoomListItemDto> roomList = new ArrayList<RoomListItemDto>();
while (rs.next()) {
String imagePath = rs.getString("image_path");
String imageName = rs.getString("image_name");
String roomName = rs.getString("room_name");
String streetAddress = rs.getString("street_address");
int rentPrice = rs.getInt("rent_price");
String roomOption = rs.getString("room_option");
RoomListItemDto dto = new RoomListItemDto(imagePath, imageName, roomName, streetAddress, rentPrice, roomOption);
roomList.add(dto);
}
return roomList;
} catch (SQLException e) {
e.printStackTrace();
return null;
} finally {
JdbcUtil.close(con, pstmt, rs);
}
}
/* room 테이블에서 전체 행 수를 계산해 반환 */
@Override
public int getTotalRoomCount() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String sql = "SELECT COUNT(*) FROM room";
try {
con = JdbcUtil.getCon();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
return rs.getInt(1); // 결과의 첫 번째 열(전체 행 수)을 정수로 반환
}
return 0; // 결과가 없다면 0을 반환
} catch (SQLException e) {
e.printStackTrace();
return 0; // 예외 발생 시 기본값으로 0을 반환
} finally {
JdbcUtil.close(con, pstmt, rs);
}
}
}
RoomServiceImpl
- 역할: 비즈니스 로직을 처리하고, DAO를 통해 데이터베이스와 상호작용합니다.
- 주요 메서드:
- getRoomList(int pageNum, int pageSize): 페이징 정보를 기반으로 방 목록을 조회합니다.
- getTotalRoomCount(): 전체 방의 수를 조회합니다.
- calculatePagination(int pageNum, int pageSize, int totalCount): 페이징 정보를 계산하여 반환합니다.
package com.mywebapp.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.mywebapp.dao.RoomDao;
import com.mywebapp.dao.RoomDaoImpl;
import com.mywebapp.dto.RoomListItemDto;
public class RoomServiceImpl implements RoomService {
// RoomDaoImpl 불러오기
private RoomDao roomDao = new RoomDaoImpl();
/* 한 페이지에 보여줄 방 목록 */
@Override
public List<RoomListItemDto> getRoomList(int pageNum, int pageSize) {
int offset = (pageNum - 1) * pageSize;
return roomDao.findAllRoomListItems(offset, pageSize);
}
/* 전체 방의 수 */
@Override
public int getTotalRoomCount() {
return roomDao.getTotalRoomCount();
}
@Override
public Map<String, Object> calculatePagination(int pageNum, int pageSize, int totalCount) {
Map<String, Object> paginationInfo = new HashMap<>();
int pageCount = (int) Math.ceil((double) totalCount / pageSize);
int startPage = ((pageNum - 1) / 10) * 10 + 1;
int endPage = Math.min(startPage + 9, pageCount);
paginationInfo.put("pageCount", pageCount);
paginationInfo.put("startPage", startPage);
paginationInfo.put("endPage", endPage);
paginationInfo.put("pageNum", pageNum);
return paginationInfo;
}
}
2. Controller (컨트롤러)
Controller는 사용자의 요청을 처리하고, Model로부터 데이터를 받아 View로 전달합니다. 여기서는 RoomListController가 컨트롤러 역할을 수행합니다.
RoomListController
- 역할: 사용자의 요청을 처리하고, 서비스 계층을 호출하여 필요한 데이터를 가져온 후, 결과를 JSP 페이지로 전달합니다.
- 주요 기능:
- 요청 파라미터로 페이지 번호를 가져옵니다.
- RoomService를 통해 방 목록과 페이징 정보를 조회합니다.
- 결과를 request 객체에 속성으로 설정하고, guestMain.jsp로 포워딩하여 클라이언트에게 응답합니다.
package com.mywebapp.controllers.user;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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.mywebapp.dao.RoomDao;
import com.mywebapp.dto.RoomListItemDto;
import com.mywebapp.model.Room;
import com.mywebapp.service.RoomService;
import com.mywebapp.service.RoomServiceImpl;
import com.mywebapp.util.JdbcUtil;
@WebServlet("/service/guestMain")
public class RoomListController extends HttpServlet {
//RoomServiceImpl 불러오기
private RoomService roomService = new RoomServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String pNum = req.getParameter("pageNum");
int pageNum = 1;
if(pNum != null) {
pageNum = Integer.parseInt(pNum);
}
int pageSize = 10; // 한 페이지에 보여줄 항목 수
// RoomServiceImpl의 메서드들 호출
List<RoomListItemDto> roomList = roomService.getRoomList(pageNum, pageSize);
int totalRoomCount = roomService.getTotalRoomCount();
Map<String, Object> paginationInfo = roomService.calculatePagination(pageNum, pageSize, totalRoomCount);
// request 객체를 통해 guestMain.jsp로 데이터들 전달
req.setAttribute("roomList", roomList);
req.setAttribute("pageCount", paginationInfo.get("pageCount"));
req.setAttribute("startPage", paginationInfo.get("startPage"));
req.setAttribute("endPage", paginationInfo.get("endPage"));
req.setAttribute("pageNum", paginationInfo.get("pageNum"));
req.getRequestDispatcher("/jsp/service/guestMain.jsp").forward(req, resp);
}
}
3. View (뷰)
View는 사용자에게 정보를 표시하는 역할을 합니다. 여기서는 guestMain.jsp가 뷰 역할을 수행합니다.
guestMain.jsp
- 역할: Controller로부터 전달받은 데이터를 시각적으로 표시합니다.
- 주요 기능:
- 방 목록 표시: c:forEach 태그를 사용하여 방 목록을 테이블 형식으로 출력합니다. 각 방의 속성(이미지 경로, 이름, 방 이름 등)을 표에 나타냅니다.
- 페이징 처리: 페이지 네비게이션을 처리하여 이전 페이지, 현재 페이지, 다음 페이지 링크를 제공합니다.
- c:if와 c:choose 태그를 사용하여 페이지 번호 링크를 동적으로 생성하고, 현재 페이지를 강조 표시합니다.
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>jsp/service/guestMain.jsp</title>
</head>
<body>
<table border="1" width="1500">
<tr>
<th>이미지 경로</th>
<th>이미지 이름</th>
<th>방 이름</th>
<th>도로명 주소</th>
<th>임대 가격</th>
<th>방 옵션</th>
</tr>
<c:forEach var="room" items="${roomList }">
<tr>
<!--
<td><img src="${room.imagePath}" alt="${room.imageName}" width="100"/></td>
-->
<td>${room.imagePath }</td>
<td>${room.imageName }</td>
<td>${room.roomName }</td>
<td>${room.streetAddress }</td>
<td>${room.rentPrice }</td>
<td>${room.roomOption }</td>
</tr>
</c:forEach>
</table>
<div>
<!-- 이전페이지 링크 -->
<c:if test="${startPage>10 }">
<a href="<%=request.getContextPath() %>/service/guestMain?pageNum=${ startPage - 1}">이전</a>
</c:if>
<!-- -->
<c:forEach var="num" begin="${startPage }" end="${endPage }" >
<c:choose>
<c:when test="${pageNum == num }"><!-- 현재 페이지 색 바꾸기 -->
<a href="<%=request.getContextPath() %>/service/guestMain?pageNum=${num }">
<span style="color:red">${num }</span>
</a>
</c:when>
<c:otherwise>
<a href="<%=request.getContextPath() %>/service/guestMain?pageNum=${num}">
<span style="color:gray">${num }</span>
</a>
</c:otherwise>
</c:choose>
</c:forEach>
<!-- 다음페이지 링크 -->
<c:if test="${endPage<pageCount }">
<a href="<%=request.getContextPath() %>/service/guestMain?pageNum=${endPage + 1}">다음</a>
</c:if>
<!-- -->
</div>
</body>
</html>
MVC 디자인 패턴의 흐름
- 사용자 요청:
- 사용자가 특정 URL에 접근하거나 폼을 제출하면, 요청은 RoomListController로 전달됩니다.
- Controller 처리:
- RoomListController는 요청을 처리하고, RoomService의 메서드를 호출하여 방 목록과 페이징 정보를 가져옵니다.
- RoomService는 RoomDaoImpl을 사용하여 데이터베이스에서 데이터를 조회합니다.
- Model 업데이트:
- RoomDaoImpl은 SQL 쿼리를 실행하여 데이터베이스와 상호작용합니다.
- RoomServiceImpl은 비즈니스 로직을 처리하고, 필요한 데이터를 계산하여 반환합니다.
- View 렌더링:
- RoomListController는 guestMain.jsp 페이지로 데이터를 포워딩합니다.
- guestMain.jsp는 전달받은 데이터를 기반으로 HTML 페이지를 생성하고, 사용자에게 결과를 표시합니다.
이 구조는 각 구성 요소의 역할을 명확히 분리하여, 코드의 유지보수성, 확장성, 재사용성을 높입니다. Model은 데이터와 비즈니스 로직을 처리하고, Controller는 요청을 처리하여 Model과 View를 연결하며, View는 최종적으로 사용자에게 결과를 표시합니다.
'세미 프로젝트' 카테고리의 다른 글
[Java/Servlet] 게스트 입장에서 mvc 패턴으로 방 계약 구현 (0) | 2024.07.30 |
---|---|
[Java/Servlet] MVC 기반으로 방 계약 구현 (0) | 2024.07.29 |
[Java/Servlet] Dao와 DaoImpl (0) | 2024.07.28 |
지도 API(Kakao Maps API)가져와서 웹에 띄우기 (0) | 2024.07.12 |