⛳️ 과제 목표
- Servlet, JSP, Java 클래스(Service/Model)로 역할을 분리한 MVC 패턴 학습
- HTTP 요청을 처리하는 구조와 데이터 전달 흐름 이해
- 데이터를 List<Menu> 형태로 관리하고 JSP에서 출력하기
📚 필수 과제
[프로젝트 구조]
practical4
ㄴ src
ㄴ MenuController
ㄴ Menu
ㄴ MenuService
ㄴ web
ㄴ WEB-INF
ㄴ web.xml
ㄴ form.jsp
ㄴ index.jsp
ㄴ list.jsp
ㄴ select.jsp
1. 점심 메뉴 등록 폼 페이지 구성
더보기
더보기
- form.jsp
- 사용자로부터 메뉴 이름, 맵기, 가격 등의 정보를 입력받습니다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>점심메뉴 등록</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.container { max-width:900px; margin:40px auto; padding:24px;
background: rgba(118, 196, 106, 0.74); border-radius:16px;
box-shadow:0 10px 24px rgba(0,0,0,.06); text-align:center;}
.btn-submit { margin: 10px 0 auto auto; padding: 12px 18px; border:0; border-radius:5px; cursor:pointer;
align-items: center;}
.form-wrap { text-align:left; margin-top:18px; }
.form-wrap .form-label { font-weight:600; }
.actions { display:flex; gap:8px; margin-top:14px; }
</style>
</head>
<body>
<div class="container" style="width: 500px;">
<h2>🍱 점심 메뉴 등록 화면</h2>
<form action="${pageContext.request.contextPath}/menu" method="post">
<div class="form-wrap">
<div class="mb-3">
<label class="form-label" for="name">음식명</label>
<input id="name" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" for="spice">맵기</label>
<select id="spice" name="spice" class="form-select">
<option value="MILD">순함</option>
<option value="NORMAL">보통</option>
<option value="HOT">매움</option>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="price">가격</label>
<input id="price" name="price" class="form-control" type="number" min="0" required>
</div>
<button type="submit" class="btn btn-primary w-100">등록</button>
<div class="actions">
<a class="btn btn-outline-light flex-fill" href="${pageContext.request.contextPath}/menu?action=list">메뉴 목록</a>
<a class="btn btn-outline-dark flex-fill" href="${pageContext.request.contextPath}/menu?action=select">랜덤 추천</a>
</div>
</div>
</form>
</div>
</body>
</html>
스타일시트는 BootStrap 기반의 css 기능을 활용하였고, 상세한 css 세팅까지는 오랜 시간이 드는 관계로 ChapGPT의 도움을 적극적으로 받았음
form 형태의 입력값을 받고 입력받은 값을 Post 할 수 있게 기능을 설정하였음
맵기의 단계는 문제의 요건에 따라 순함, 보통, 매움으로 구분하여 진행하였음
2. 메뉴 등록 요청 처리
더보기
더보기
- MenuController로 입력값을 받아 MenuService로 전달하고 메뉴 리스트를 추가합니다.
- MenuService에는 static한 menuList가 있습니다.
- static ?
- static은 공용이라는 뜻이에요.
- 클래스가 만들어질 때 딱 하나만 만들어지고, 모든 사람이 같이 쓰는 것이라고 생각하면 돼요.
- final?
- 변하지 않는 값이에요.
//MenuController.java
package practical4;
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 java.io.IOException;
@WebServlet("/menu")
public class MenuController extends HttpServlet {
private MenuService service = new MenuService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
System.out.println("[GET] /menu action=" + action);
// 1) 등록 폼
if ("form".equals(action)) {
System.out.println("[ROUTE] forward -> /form.jsp");
request.getRequestDispatcher("/form.jsp").forward(request, response);
return;
}
// 2) 조건 선택 화면
if ("select".equals(action)) {
System.out.println("[ROUTE] forward -> /select.jsp");
request.getRequestDispatcher("/select.jsp").forward(request, response);
return;
}
// 3) 기본: 목록 화면
request.setAttribute("menus", service.getMenuList());
System.out.println("[ROUTE] forward -> /list.jsp (size=" + (service.getMenuList() == null ? 0 : service.getMenuList().size()) + ")");
request.getRequestDispatcher("/list.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String action = request.getParameter("action");
// 조건 추천 처리: POST /menu?action=recommend
if ("recommend".equals(action)) {
String basis = request.getParameter("basis"); // price | spice
String detail = request.getParameter("detail"); // P0_10.. | MILD/NORMAL/HOT
java.util.List<Menu> all = service.getMenuList();
java.util.List<Menu> candidates = new java.util.ArrayList<Menu>();
if ("price".equals(basis)) {
int min = 0, max = Integer.MAX_VALUE;
if ("P0_10".equals(detail)) { min = 0; max = 10000; }
else if ("P10_30".equals(detail)) { min = 10000; max = 30000; }
else if ("P30_50".equals(detail)) { min = 30000; max = 50000; }
else if ("P50_UP".equals(detail)) { min = 50000; max = Integer.MAX_VALUE; }
for (Menu m : all) if (m.getPrice() >= min && m.getPrice() < max) candidates.add(m);
} else if ("spice".equals(basis)) {
for (Menu m : all) if (detail != null && detail.equals(m.getSpice())) candidates.add(m);
}
Menu picked = null;
if (!candidates.isEmpty()) {
picked = candidates.get(new java.util.Random().nextInt(candidates.size()));
}
if (picked != null) request.setAttribute("menu", picked);
else request.setAttribute("emptyMsg", "조건에 맞는 메뉴가 없어요. 먼저 등록해 주세요!");
request.getRequestDispatcher("/select.jsp").forward(request, response);
return;
}
// 기본: 등록 처리 (POST /menu)
String name = request.getParameter("name");
String spice = request.getParameter("spice");
int price = Integer.parseInt(request.getParameter("price"));
service.addMenu(new Menu(name, spice, price));
response.sendRedirect(request.getContextPath() + "/menu?action=list");
}
}
기본 WebServlet 개념과 기능을 적용하여 POST / GET 형태로 요청을 받고 요청받은 값을 그대로 보여주는 형태로 구성하였음
3. 메뉴 목록 조회 페이지
더보기
더보기
- List<Menu> 데이터를 JSP에서 반복문으로 출력합니다.
- 등록된 메뉴들의 이름, 맵기, 가격을 테이블 형태로 출력됩니다.
//list.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List, practical4.Menu" %>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>점심메뉴 목록</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container py-4">
<h2 class="mb-4">🍱 점심메뉴 목록</h2>
<%
// 컨트롤러에서 넘겨준 속성들
List<Menu> menus = (List<Menu>) request.getAttribute("menus");
%>
<!-- 메뉴 테이블 -->
<% if (menus == null || menus.isEmpty()) { %>
<div class="card mb-3 text-center p-4">
<div class="mb-2">😢 등록된 점심 메뉴가 아직 없어요!</div>
</div>
<% } else { %>
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead class="table-light">
<tr>
<th style="width:80px;">#</th>
<th>이름</th>
<th style="width:140px;">맵기</th>
<th style="width:160px;">가격</th>
</tr>
</thead>
<tbody>
<% for (int i = 0; i < menus.size(); i++) { Menu m = menus.get(i); %>
<tr>
<td><%= i + 1 %></td>
<td><%= m.getName() %></td>
<td><%= m.getSpice() %></td>
<td><%= m.getPrice() %>원</td>
</tr>
<% } %>
</tbody>
</table>
</div>
<% } %>
<div class="mt-4 d-flex gap-2">
<a class="btn btn-primary" href="<%= request.getContextPath() %>/menu?action=form">메뉴 등록</a>
<a class="btn btn-success" href="<%= request.getContextPath() %>/menu?action=select">조건 기반 추천</a>
</div>
</body>
</html>
❤️🔥 도전 과제
1. 랜덤 추천 기능
더보기
더보기
- 점심 메뉴를 고르기 어려운 사용자를 위한 자동 추천 기능 제공합니다.
- select.jsp를 추가하고, 추천을 위한 선택 조건을 선택하는 화면을 만듭니다.
- <select>를 두개 만듭니다.
- 첫번째 <select>에는 총 2개의 option이 있습니다. (가격, 맵기)
- 두번째 <select>는 첫번째 select 값에 따라 동적으로 변경됩니다.
- 가격 선택시 0~10,000 | 10,000~30,000 | 30,000~50,000 | 50,000~ ****총 4개의 option이 뜹니다.
- 맵기 선택시 매움, 보통, 순함 총 3개의 option이 뜹니다.
//select.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="practical4.Menu" %>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>조건 기반 추천</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container py-4">
<h2 class="mb-4">조건 기반 추천</h2>
<%
Menu menu = (Menu) request.getAttribute("menu");
String emptyMsg = (String) request.getAttribute("emptyMsg");
%>
<% if (menu != null) { %>
<div class="card mb-3 shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-1">오늘의 추천</h5>
<div class="card-text">
<strong><%= menu.getName() %></strong>
<span class="ms-2 text-muted">(맵기: <%= menu.getSpice() %> / 가격: <%= menu.getPrice() %>원)</span>
</div>
</div>
<a class="btn btn-outline-primary btn-sm" href="<%= request.getContextPath() %>/menu?action=select">다시 선택</a>
</div>
</div>
<% } else if (emptyMsg != null) { %>
<div class="card mb-3 text-center p-4">
<div class="mb-2">😢 <%= emptyMsg %></div>
<a class="btn btn-success" href="<%= request.getContextPath() %>/menu?action=form">메뉴 등록하러 가기</a>
</div>
<% } %>
<form action="<%= request.getContextPath() %>/menu?action=recommend" method="post" class="row g-3">
<div class="col-12 col-md-4">
<label class="form-label" for="basis">기준</label>
<select id="basis" name="basis" class="form-select" required>
<option value="" disabled selected>-- 선택 --</option>
<option value="price">가격</option>
<option value="spice">맵기</option>
</select>
</div>
<div class="col-12 col-md-4">
<label class="form-label" for="detail">세부</label>
<select id="detail" name="detail" class="form-select" required disabled>
<option value="" disabled selected>먼저 기준을 선택하세요</option>
</select>
</div>
<div class="col-12 col-md-4 align-self-end">
<button type="submit" class="btn btn-primary w-100">추천 받기</button>
</div>
</form>
<div class="mt-4 d-flex gap-2">
<a class="btn btn-outline-secondary" href="<%= request.getContextPath() %>/menu?action=list">목록</a>
<a class="btn btn-outline-dark" href="<%= request.getContextPath() %>/menu?action=form">등록</a>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const basis = document.getElementById('basis');
const detail = document.getElementById('detail');
const options = {
price: [
{v:'P0_10', t:'0~10,000원'},
{v:'P10_30', t:'10,000~30,000원'},
{v:'P30_50', t:'30,000~50,000원'},
{v:'P50_UP', t:'50,000원 이상'}
],
spice: [
{v:'MILD', t:'순함'},
{v:'NORMAL', t:'보통'},
{v:'HOT', t:'매움'}
]
};
basis.addEventListener('change', () => {
const list = options[basis.value] || [];
detail.innerHTML = '';
if (list.length === 0) {
detail.disabled = true;
const opt = document.createElement('option');
opt.value=''; opt.textContent='해당 옵션이 없습니다';
opt.disabled = true; opt.selected = true;
detail.appendChild(opt);
return;
}
list.forEach(o => {
const opt = document.createElement('option');
opt.value = o.v; opt.textContent = o.t;
detail.appendChild(opt);
});
detail.disabled = false;
});
});
</script>
</body>
</html>
2. 추천 결과를 카드 형태로 스타일링
더보기
더보기
- 추천 메뉴는 Bootstrap 카드 스타일로 이쁘게 표시 (이름, 설명, 감정 이모지 등 포함) 합니다.
//list.jsp 에서 사용
<% if (menus == null || menus.isEmpty()) { %>
<div class="card mb-3 text-center p-4">
<div class="mb-2">😢 등록된 점심 메뉴가 아직 없어요!</div>
</div>
//select.jsp 에서 사용
else if (emptyMsg != null) { %>
<div class="card mb-3 text-center p-4">
<div class="mb-2">😢 <%= emptyMsg %></div>
<a class="btn btn-success" href="<%= request.getContextPath() %>/menu?action=form">메뉴 등록하러 가기</a>
</div>
<% } %>
3. 등록된 메뉴가 없을 때 안내 페이지로 연결 시키기
더보기
더보기
- 등록된 메뉴가 없을 때 random.jsp나 list.jsp를 접근했을 때 새로 띄워줄 화면을 만들어보세요.
- 예시 : ‘등록된 메뉴가 없어요! 등록을 먼저 진행해주세요!’ 문구 띄우는 안내 페이지로 연결되도록 처리
random.jsp 는 따로 진행하지 않음 (select.jsp에서 조건 기반 랜덤하게 출력을 해줌)
위의 기재한 방식과 같게 값이 공백이거나 null 값일 때 메세지를 노출시키도록 진행하였음
☠️ 트러블 슈팅
- 발생한 Issue
- ① 전반적인 jsp 화면 구성이 원활하게 나타나지 않는 상황
- 폼 제출 이후 화면이 하얗게 나오거나 비어있게 보이는 상황
- ② form 화면을 놓고 등록버튼을 눌렀을 때 list 가 저장되지 않는 상황
- ③ list가 저장되고 난 이후에 조건 기반 추천을 누르면 추천을 하지 않고 Empty인 경우만 보여주는 상황
- ④ 수정과정 중 어느 단계에서의 문제점인지 몰라 전반적인 재작성 중 코드가 완전히 꼬여버린 상황
- ⑤ 구성하고자 하는 파일의 구조와 각 파일의 역할에 대해 정확하게 이해하지 못하고 문제를 풀다보니 코드 구성 자체가 꼬이거나 어디를 손대야 할지 모르겠었음
- ① 전반적인 jsp 화면 구성이 원활하게 나타나지 않는 상황
- 해결방법 / 찾아본 내용
- ① JSP로 직접 링크를 걸게 되어 Controller를 통해 처리되지 않아 발생한 이슈
- 컨트롤러 경유 URL 을 사용하여 request.setAttribute("menus")를 넣어 이동할 수 있게 해야함
- ②,③ value / 라벨 불일치로 인하여 조건에 매칭되지 않아 계속 Empty인 상황일 때
- ④ 문제가 발생한 프로젝트는 그대로 유지하고 우선 새로운 프로젝트로 문제를 다시 접근하고 해결해보고자 함 (문제를 찾는 것 또한 중요)
현재 문제점은 목록까지는 저장을 잘 하나 조건 기반 추천이 아예 진행이 안되는 상태- 주요 문제점은 Controller를 통해 버튼 작동 및 페이지의 연결이 되도록 올바른 명령어를 써야한다는 것
- 진행 도중 디버그 기능을 적극 활용하여 어느 수행단계에서 오류가 발생하는지에 대해서 확실하게 알 것
- IntelliJ 콘솔창에서도 친절하게 위치를 알려주긴 하나, 어떤 식으로 바꿔야 하는지에 대해서도 열심히 방법을 찾아봐야 함
- ⑤ 구성하고자 하는 파일의 구조와 각 파일의 역할에 대해 먼저 정리하고 DTO, Controller 등 어떤 기능들을 먼저 담아야 하는지 정리하고 코드 작성에 들어갈 것 !!
- ① JSP로 직접 링크를 걸게 되어 Controller를 통해 처리되지 않아 발생한 이슈
'JAVA > Java 실습' 카테고리의 다른 글
| [JAVA] 계산기 과제 개념 추가 정리 (0) | 2025.09.16 |
|---|---|
| 점메추 SPA 에디션 (0) | 2025.09.10 |
| JSP 자기소개 버전 만들기 (0) | 2025.09.09 |
| 나를 소개합니다 (0) | 2025.09.08 |
| JSP / Servlet 실습 (0) | 2025.09.05 |