[아이티센 부트캠프] Servlet 실습 2

이언덕·2026년 4월 24일

아이티센 부트캠프

목록 보기
48/115
post-thumbnail

문제

먼저 문제 원본은 아래와 같다.

문제에서 실제로 구현해야 하는 조건만 다시 정리하면 이렇다.

  • POST 요청을 처리할 Servlet을 만든다.
  • 이번 실습에서는 ReservationServletPractice 클래스와 /reservationPractice 매핑으로 진행한다.
  • 예약 폼 화면을 만들고 action 경로가 Servlet 매핑과 맞게 연결되도록 작성한다.
  • 예약자명, 룸 선택, 추가 요청 사항, 예약 날짜 값을 서버에서 받아야 한다.
  • 체크박스처럼 여러 개 선택되는 값은 배열로 받아야 한다.
  • 전달받은 날짜 문자열은 LocalDate로 바꾼 뒤 원하는 형식으로 출력한다.
  • 마지막에는 브라우저 화면에 예약 결과를 출력해야 한다.


문제 해석

이 문제는 단순히 예약 폼 화면만 만드는 문제가 아니다.
브라우저에서 입력한 예약 정보가 POST 방식으로 서버에 전달되고, 서버가 그 값을 받아 다시 결과 화면으로 보여주는 전체 흐름을 구현하는 문제다.


처음에는 Servlet을 만들고 URL mapping을 먼저 잡아 둬야 한다.
그다음 HTML 폼에서 action 경로와 입력 요소의 name 속성을 맞춰서 요청이 정상적으로 전달되게 해야 한다.
이후에는 doPost()에서 값을 받아 콘솔로 먼저 확인하고, 마지막에는 날짜 형식을 바꾸고 체크박스 값도 보기 좋게 정리해서 브라우저에 출력하면 된다.


즉 이 실습의 핵심은 아래 흐름으로 정리할 수 있다.
Servlet 생성 → URL mapping 설정 → 예약 폼 작성 → 경로 연결 확인 → 값 전달 확인 → 날짜 형식 변환 → 체크박스 배열 정리 → 브라우저 출력


풀이 순서

  1. 연습용 Servlet 클래스 ReservationServletPractice를 만들고 URL mapping을 /reservationPractice로 설정한다.
  2. 예약 폼 HTML을 작성하고 action="/edu/reservationPractice"로 연결해서 404 없이 요청이 들어가는지 확인한다.
  3. doPost()에서 예약자명, 룸, 추가 요청 사항, 날짜 값을 받아 콘솔에 출력해 값 전달을 확인한다.
  4. 날짜 문자열을 LocalDate로 바꾸고, 체크박스 배열은 마지막 쉼표 없이 정리해서 브라우저 화면에 출력한다.



자세한 풀이

1. ReservationServletPractice 생성과 URL mapping 설정

먼저 POST 요청을 받아 줄 Servlet 클래스를 만들었다.
이번 실습에서는 연습용으로 클래스 이름을 ReservationServletPractice로 만들고, 브라우저 요청 경로는 /reservationPractice로 맞췄다.
즉, 브라우저가 이 경로로 요청을 보내면 이 클래스가 그 요청을 처리하게 되는 구조다.

package practiceLab;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/reservationPractice") // 브라우저 요청 경로와 연결
public class ReservationServletPractice extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // `POST` 요청이 들어오면 여기서 처리할 예정
    }
}

이 단계에서는 아직 값을 받는 로직까지 넣지 않았다.
먼저 어떤 요청을 이 클래스가 담당하는지 연결만 잡아 두는 단계라고 보면 된다.

데이터 흐름 예상

<form method="post" action="/edu/reservationPractice">
  <!-- 1. 브라우저가 `/edu/reservationPractice`로 `POST` 요청을 보냄 -->
</form>
@WebServlet("/reservationPractice") // 2. 서버가 이 매핑을 보고 처리할 `Servlet`을 찾음
public class ReservationServletPractice extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        // 3. `POST` 요청이므로 `doPost()`가 실행됨
    }
}

→ 사용자가 폼을 제출하면 브라우저가 /edu/reservationPractice로 요청을 보낸다.
→ 서버는 @WebServlet("/reservationPractice")를 보고 어떤 Servlet이 이 요청을 처리해야 하는지 찾는다.
→ 요청 방식이 POST이기 때문에 doPost()가 호출된다.



2. 예약 폼 작성과 경로 연결 확인

다음으로는 브라우저에서 값을 입력할 수 있는 예약 폼을 작성했다.
이번 단계의 핵심은 값을 완벽하게 처리하는 것이 아니라, action 경로와 Servlet 매핑이 제대로 연결되어 404가 뜨지 않는지 확인하는 것이었다.
그래서 form의 method를 post로 두고, action을 /edu/reservationPractice로 맞췄다.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Insert title here</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Barriecito&family=Bitcount+Grid+Double:wght@100..900&family=Kirang+Haerang&display=swap"
      rel="stylesheet"
    />
    <style>
      body {
        display: flex;
        justify-content: center;
      }
      #form {
        background-color: orange;
        padding: 20px 35px;
        border-radius: 6px;
      }
      .kirang-haerang-regular {
        font-family: 'Kirang Haerang', system-ui;
        font-weight: 400;
        font-style: normal;
      }
      h2 {
        text-shadow: 2px 5px 10px orange;
      }
    </style>
  </head>
  <body>
    <div class="kirang-haerang-regular">
      <h2 style="text-align: center" class="kirang-haerang-regular">펜션 예약 서비스</h2>
      <hr />
      <div id="form">
        <form method="post" action="/edu/reservationPractice">
          <label>예약자명 :</label>
          <input type="text" name="reservationName" />
          <br />
          <br />
          <label>예약암호 :</label>
          <input type="password" name="reservationPassword" />
          <br />
          <br />
          <label>룸선택 :</label>
          <input id="dooly" name="room" value="둘리" type="radio" />
          <label for="dooly">둘리</label>
          <input id="heedong" name="room" value="희동" type="radio" />
          <label for="heedong">희동</label>
          <input id="dowoo" name="room" value="도우너" type="radio" />
          <label for="dowoo">도우너</label>
          <input id="ddochi" name="room" value="또치" type="radio" />
          <label for="ddochi">또치</label>
          <br />
          <br />
          <label>추가요청사항 :</label>
          <label>
            <input type="checkbox" value="바베큐" name="option" />
            바베큐
          </label>
          <label>
            <input type="checkbox" value="수영장" name="option" />
            수영장
          </label>
          <label>
            <input type="checkbox" value="낚시" name="option" />
            낚시
          </label>
          <label>
            <input type="checkbox" value="조식" name="option" />
            조식
          </label>
          <br />
          <br />
          <label>예약날짜 :</label>
          <input type="date" name="date" />
          <hr />
          <input type="submit" value="보내기" />
          <input type="reset" value="리셋" />
        </form>
      </div>
    </div>
  </body>
</html>

여기서 중요한 건 각 입력값이 서버에서 읽을 수 있도록 name 속성이 들어가 있다는 점이다.
또 radio와 checkbox에는 value를 넣어 두었기 때문에, 서버에서는 화면에 보이는 이름 그대로 값을 받을 수 있다.

데이터 흐름

<form method="post" action="/edu/reservationPractice">
  <!-- 1. 제출 버튼을 누르면 `/edu/reservationPractice`로 이동 -->
  <input type="text" name="reservationName" />
  <!-- 2. 예약자명이 `reservationName`이라는 이름으로 전달됨 -->
  <input type="password" name="reservationPassword" />
  <!-- 3. 예약암호가 `reservationPassword`라는 이름으로 전달됨 -->
  <input id="dooly" name="room" value="둘리" type="radio" />
  <!-- 4. 선택한 룸 값이 `room`으로 전달됨 -->
  <input type="checkbox" value="바베큐" name="option" />
  <!-- 5. 체크한 추가 요청 사항들이 `option` 배열로 전달됨 -->
  <input type="date" name="date" />
  <!-- 6. 날짜 값이 `date`라는 이름으로 전달됨 -->
</form>

→ 사용자가 폼에 값을 입력한 뒤 제출 버튼을 누르면 /edu/reservationPractice로 요청이 전송된다.
→ 이 단계에서는 제출했을 때 404가 뜨지 않는지를 먼저 확인했다.
→ 즉, 두 번째 수행의 목적은 값 가공보다 경로 연결 확인에 있었다.



3. doPost()에서 값 전달 확인하기

경로 연결이 확인된 다음에는 실제로 값이 Servlet까지 잘 들어오는지 확인했다.
이번 단계에서는 우선 브라우저 출력보다 콘솔 출력으로 값이 정상 전달되는지 확인하는 데 집중했다.

package practiceLab;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

@WebServlet("/reservationPractice")
public class ReservationServletPractice extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        String reservationName = request.getParameter("reservationName");
        String room = request.getParameter("room");
        String option[] = request.getParameterValues("option");
        String date = request.getParameter("date");

        System.out.println(reservationName);
        System.out.println(room);
        System.out.println(Arrays.toString(option));
        System.out.println(date);
    }
}

여기서 reservationName, room, date는 하나의 값이므로 getParameter()로 받았다.
반면 option은 여러 개 선택될 수 있으므로 getParameterValues()로 배열 형태로 받았다.

데이터 흐름

String reservationName = request.getParameter("reservationName");
// 1. 예약자명 입력값을 하나의 문자열로 받음

String room = request.getParameter("room");
// 2. 선택한 룸 값을 문자열로 받음

String option[] = request.getParameterValues("option");
// 3. 체크한 추가 요청 사항을 문자열 배열로 받음

String date = request.getParameter("date");
// 4. 날짜 값을 문자열로 받음

System.out.println(reservationName);
System.out.println(room);
System.out.println(Arrays.toString(option));
System.out.println(date);
// 5. 전달된 값을 콘솔에서 먼저 확인함

→ 예약자명은 문자열 하나로 전달된다.
→ 룸 선택값도 문자열 하나로 전달된다.
→ 체크박스는 여러 개 선택될 수 있어서 배열로 전달된다.
→ 날짜는 먼저 문자열로 받은 뒤, 다음 단계에서 원하는 형식으로 바꾸게 된다.

트러블 슈팅

처음에는 체크박스 배열을 그냥 출력해서 확인했다.

System.out.println(option);

이렇게 출력하면 배열 안의 실제 값이 아니라 [Ljava.lang.String;@... 같은 형태로 보인다.
즉, 배열 주소 정보처럼 보이는 값만 출력되기 때문에 어떤 옵션이 선택됐는지 한눈에 확인하기 어렵다.
그래서 아래처럼 Arrays.toString()으로 바꿔서 출력했다.

System.out.println(Arrays.toString(option));

이제는 [바베큐, 수영장]처럼 실제 선택값이 보이기 때문에 전달 여부를 훨씬 쉽게 확인할 수 있다.
즉, 이번 단계에서는 체크박스 배열은 그냥 출력하지 말고 Arrays.toString()으로 확인하는 게 더 정확하다는 점을 같이 확인했다.



4. 날짜 형식 변환과 브라우저 출력 완성

값 전달이 확인된 다음에는 마지막으로 브라우저 화면에 예약 결과를 출력했다.
이 단계에서는 날짜 문자열을 LocalDate로 바꾸고, DateTimeFormatter를 사용해서 yyyy년 MM월 dd일 형식으로 바꿨다.
또 추가 요청 사항은 여러 개가 선택될 수 있기 때문에, 마지막 값 뒤에는 쉼표가 붙지 않도록 정리했다.

package practiceLab;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

@WebServlet("/reservationPractice")
public class ReservationServletPractice extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        String reservationName = request.getParameter("reservationName");
        String room = request.getParameter("room");
        String option[] = request.getParameterValues("option");
        String date = request.getParameter("date");

        LocalDate localDate = LocalDate.parse(date);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        String formattedDate = localDate.format(formatter);

        System.out.println(reservationName);
        System.out.println(room);
        System.out.println(Arrays.toString(option));
        System.out.println(date);

        out.print("<h1 style='color: blue'>" + reservationName + "님의 예약 내용 </h1>");
        out.print("<hr />");
        out.print("<ul>");
        out.print("<li> 선택한 룸: " + room + "</li>");
        if (option != null && option.length != 0) {
            out.print("<li> 추가 요청 사항: ");
            for (int i = 0; i < option.length; i++) {
                out.printf("%s%s", option[i], i == option.length - 1 ? "" : ", ");
            }
            out.print("</li>");
        } else {
            out.print("<li>옵션을 고르지 않았습니다.</li>");
        }
        out.printf("<li>예약 날짜: " + formattedDate + "</li>");
        out.print("</ul>");
    }
}

이제는 단순히 값이 들어오는지만 확인하는 게 아니라, 사용자가 보기 좋은 결과 화면으로 정리해서 출력하는 단계가 됐다.
특히 날짜 형식 변환과 체크박스 출력 정리가 이번 단계의 핵심이었다.

데이터 흐름

String date = request.getParameter("date");
// 1. 날짜 값을 문자열로 받음

LocalDate localDate = LocalDate.parse(date);
// 2. 문자열 날짜를 `LocalDate` 객체로 변환함

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
String formattedDate = localDate.format(formatter);
// 3. 날짜를 원하는 형식의 문자열로 다시 만듦

if (option != null && option.length != 0) {
    out.print("<li> 추가 요청 사항: ");
    for (int i = 0; i < option.length; i++) {
        out.printf("%s%s", option[i], i == option.length - 1 ? "" : ", ");
    }
    out.print("</li>");
} else {
    out.print("<li>옵션을 고르지 않았습니다.</li>");
}
// 4. 옵션이 있으면 쉼표로 이어서 출력하고, 없으면 안내 문구를 출력함

out.print("<ul>");
out.print("<li> 선택한 룸: " + room + "</li>");
out.print("<li>예약날짜 : " + formattedDate + "</li>");
out.print("</ul>");
// 5. 마지막에 브라우저 화면용 `HTML`을 출력함

→ 날짜는 처음에 문자열로 들어온다.
→ 그 문자열을 LocalDate로 바꾼 뒤 다시 보기 좋은 형식으로 만든다.
→ 체크박스 값은 배열이기 때문에 반복문으로 하나씩 꺼내 출력한다.
→ 마지막 요소인지 확인해서 마지막 쉼표는 붙지 않도록 삼항 연산자로 정리했다.

트러블 슈팅

추가 요청 사항을 반복문으로 출력할 때 처음에는 모든 값 뒤에 쉼표가 붙는 문제가 있었다.
예를 들어 바베큐, 수영장, 조식, 처럼 마지막 값 뒤에도 쉼표가 남아 결과가 어색해졌다.
그래서 아래처럼 마지막 요소인지 확인하는 삼항 연산자를 넣어 수정했다.

for (int i = 0; i < option.length; i++) {
    out.printf("%s%s", option[i], i == option.length - 1 ? "" : ", ");
}

이렇게 바꾸면 마지막 값 뒤에는 빈 문자열이 붙고, 그 전 값들에만 , 가 붙는다.
즉, 이번 단계에서는 반복문 출력에서도 마지막 요소를 따로 처리해야 결과가 깔끔해진다는 점을 같이 확인했다.



최종 코드

reservation.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Insert title here</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Barriecito&family=Bitcount+Grid+Double:wght@100..900&family=Kirang+Haerang&display=swap"
      rel="stylesheet"
    />
    <style>
      body {
        display: flex;
        justify-content: center;
      }
      #form {
        background-color: orange;
        padding: 20px 35px;
        border-radius: 6px;
      }
      .kirang-haerang-regular {
        font-family: 'Kirang Haerang', system-ui;
        font-weight: 400;
        font-style: normal;
      }
      h2 {
        text-shadow: 2px 5px 10px orange;
      }
    </style>
  </head>
  <body>
    <div class="kirang-haerang-regular">
      <h2 style="text-align: center" class="kirang-haerang-regular">펜션 예약 서비스</h2>
      <hr />
      <div id="form">
        <form method="post" action="/edu/reservationPractice">
          <label>예약자명 :</label>
          <input type="text" name="reservationName" />
          <br />
          <br />
          <label>예약암호 :</label>
          <input type="password" name="reservationPassword" />
          <br />
          <br />
          <label>룸선택 :</label>
          <input id="dooly" name="room" value="둘리" type="radio" />
          <label for="dooly">둘리</label>
          <input id="heedong" name="room" value="희동" type="radio" />
          <label for="heedong">희동</label>
          <input id="dowoo" name="room" value="도우너" type="radio" />
          <label for="dowoo">도우너</label>
          <input id="ddochi" name="room" value="또치" type="radio" />
          <label for="ddochi">또치</label>
          <br />
          <br />
          <label>추가요청사항 :</label>
          <label>
            <input type="checkbox" value="바베큐" name="option" />
            바베큐
          </label>
          <label>
            <input type="checkbox" value="수영장" name="option" />
            수영장
          </label>
          <label>
            <input type="checkbox" value="낚시" name="option" />
            낚시
          </label>
          <label>
            <input type="checkbox" value="조식" name="option" />
            조식
          </label>
          <br />
          <br />
          <label>예약날짜 :</label>
          <input type="date" name="date" />
          <hr />
          <input type="submit" value="보내기" />
          <input type="reset" value="리셋" />
        </form>
      </div>
    </div>
  </body>
</html>

ReservationServletPractice.java

package practiceLab;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

@WebServlet("/reservationPractice")
public class ReservationServletPractice extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        String reservationName = request.getParameter("reservationName");
        String room = request.getParameter("room");
        String option[] = request.getParameterValues("option");
        String date = request.getParameter("date");
        LocalDate localDate = LocalDate.parse(date);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        String formattedDate = localDate.format(formatter);

        System.out.println(reservationName);
        System.out.println(room);
        System.out.println(Arrays.toString(option));
        System.out.println(date);

        out.print("<h1 style='color: blue'>" + reservationName + "님의 예약 내용 </h1>");
        out.print("<hr />");
        out.print("<ul>");
        out.print("<li> 선택한 룸: " + room + "</li>");
        if (option != null && option.length != 0) {
            out.print("<li> 추가 요청 사항: ");
            for (int i = 0; i < option.length; i++) {
                out.printf("%s%s", option[i], i == option.length - 1 ? "" : ", ");
            }
            out.print("</li>");
        } else {
            out.print("<li>옵션을 고르지 않았습니다.</li>");
        }
        out.print("<li>예약날짜 : " + formattedDate + "</li>");
        out.print("</ul>");
    }
}



실습하면서 확인한 점

  • form의 action 경로와 @WebServlet 매핑 이름은 정확히 맞아야 한다.
  • 입력값을 서버로 보내려면 각 입력 요소에 name 속성이 꼭 필요하다.
  • 체크박스처럼 여러 개 선택될 수 있는 값은 getParameterValues()로 받아야 한다.
  • 배열을 콘솔에 바로 출력하면 내용이 잘 보이지 않기 때문에 Arrays.toString()으로 확인하는 것이 좋다.
  • 날짜 문자열은 LocalDate.parse()로 바꾼 뒤 DateTimeFormatter로 원하는 형식으로 다시 출력할 수 있다.
  • 반복문으로 값을 이어서 출력할 때는 마지막 요소 뒤 쉼표를 따로 처리해야 결과가 깔끔하다.
  • 이번 실습에서는 경로 연결 확인 → 값 전달 확인 → 결과 화면 출력 순서로 나누어 구현하니 흐름이 훨씬 명확하게 정리됐다.

0개의 댓글