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

이언덕·2026년 4월 26일

아이티센 부트캠프

목록 보기
53/115
post-thumbnail

문제

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

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

  • 로또 번호를 입력하는 HTML 폼을 사용한다.
  • 이번 실습에서는 lottoForm3.html, lottoResult2.jsp는 이전 파일을 복사해서 재사용한다.
  • 실제로 새롭게 구현하는 핵심은 LottoServlet3의 로직이다.
  • 사용자가 입력한 로또 숫자는 GET 방식으로 전달받아야 한다.
  • 서버는 1~6 사이 랜덤값을 하나 만들어 비교해야 한다.
  • 응모 횟수는 session에 저장해서 누적 관리해야 한다.
  • 3번까지는 응모할 수 있고, 4번째부터는 더 이상 응모할 수 없어야 한다.
  • 실패하면 다시 돌아가기 링크가 보여야 한다.
  • 당첨되거나 응모 제한에 걸리면 다시 돌아가기 링크가 보이지 않아야 한다.
  • 마지막에는 같은 JSP로 forward해서 결과를 출력해야 한다.


문제 해석

이번 문제는 앞선 lottoPractice2 실습을 그대로 확장한 문제다.
입력 폼과 결과 JSP는 거의 그대로 재사용하고, Servlet에 응모 횟수 제한 조건만 추가한 형태라고 보면 된다.


즉 이번 실습의 핵심은 화면을 새로 만드는 데 있지 않다.
이미 만들어 둔 HTML, JSP 구조 위에 session을 이용한 횟수 제한 로직을 붙이는 데 있다.
그래서 이번 문제를 이해할 때는 request와 session의 역할 차이를 같이 봐야 한다.


request는 한 번의 요청에서만 쓸 결과 메시지, 이미지 주소, 링크 표시 여부를 넘길 때 사용한다.
반면 session은 여러 번 요청해도 계속 기억해야 하는 응모 횟수를 저장할 때 사용한다.
즉 이번 문제는 request는 결과 출력용, session은 횟수 누적용으로 역할이 나뉘는 문제다.


즉 이 실습의 흐름은 아래처럼 정리할 수 있다.
기존 HTML, JSP 재사용 → 숫자 전달 → 랜덤값 생성 → session에서 응모 횟수 확인 → 결과 비교 → request에 결과 저장 → 같은 JSP로 forward


풀이 순서

  1. 이전 실습에서 사용한 lottoForm3.html과 lottoResult2.jsp를 그대로 복사해서 재사용한다.
  2. LottoServlet3에서 입력값과 랜덤값을 만든다.
  3. session에서 lottoCount를 꺼내고, 처음 접근이면 기본값을 설정한다.
  4. 응모 횟수가 4 이상인지 먼저 확인한다.
  5. 아직 응모 가능하면 당첨 여부를 비교해서 msg, imginfo, display 값을 저장한다.
  6. 실패면 응모 횟수를 +1, 당첨이면 다음부터 바로 막히도록 +3 처리한다.
  7. 마지막에는 같은 JSP로 forward해서 결과를 출력한다.



자세한 풀이

1. 기존 HTML, JSP 파일 재사용

이번 실습에서는 입력 폼과 결과 화면을 새로 만들지 않았다.
이전 실습에서 사용한 lottoForm3.html, lottoResult2.jsp를 복사해서 그대로 사용했고, 연결 경로만 이번 실습에 맞게 유지했다.
즉 이번 단계의 핵심은 새 화면 구현이 아니라, 기존 화면을 재사용하면서 Servlet 로직만 확장하는 것이었다.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>로또 번호를 맞춰 보세요!!!</h1>
    <hr />
    <form method="get" action="/edu/lotto3">
      <label for="lottoNum" style="font-size: 25px">1부터 6까지의 숫자를 입력하세요: </label>
      <input
        type="number"
        id="lottoNum"
        name="lottoNum"
        min="1"
        max="6"
        step="1"
        required
        style="width: 60px; height: 30px; font-size: 18px"
      />
      <input
        type="image"
        src="../images/clover.png"
        alt="로또 추첨"
        style="width: 50px; height: 50px"
      />
    </form>
  </body>
</html>
<%@ 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><%= request.getAttribute("msg") %></h1>
<img src=<%= request.getAttribute("imginfo") %> />
<a style="display:<%= request.getAttribute("display") %>;" href="/edu/clientexam/lottoForm3.html">다시 돌아가기</a>
</body>
</html>

이 단계에서 확인할 포인트는 두 가지다.
입력 폼은 /edu/lotto3으로 요청을 보내야 하고, 결과 JSP는 msg, imginfo, display를 출력할 준비가 되어 있어야 한다.

데이터 흐름 예상

<form method="get" action="/edu/lotto3">
  <!-- 1. 브라우저가 `/edu/lotto3`으로 `GET` 요청을 보냄 -->
  <input type="number" id="lottoNum" name="lottoNum" min="1" max="6" step="1" required />
  <!-- 2. 사용자가 입력한 숫자가 `lottoNum`이라는 이름으로 서버에 전달됨 -->
  <input type="image" src="../images/clover.png" alt="로또 추첨" />
  <!-- 3. 클로버 이미지를 누르면 제출 버튼처럼 동작함 -->
</form>
<h1><%= request.getAttribute("msg") %></h1>
<img src=<%= request.getAttribute("imginfo") %> />
<a style="display:<%= request.getAttribute("display") %>;" href="/edu/clientexam/lottoForm3.html">다시 돌아가기</a>

→ 입력 폼은 숫자를 서버로 보내는 역할을 한다.
→ 결과 JSP는 서버가 넘긴 값을 그대로 꺼내 화면에 출력하는 역할을 한다.
→ 즉 이번 실습에서는 화면 파일보다 Servlet 로직이 핵심이라는 점을 먼저 정리해 둘 수 있다.



2. session에서 응모 횟수 가져오기

이번 실습의 핵심은 응모 횟수를 계속 기억해야 한다는 점이었다.
한 번 요청하고 끝나는 값이 아니라, 사용자가 몇 번 시도했는지를 다음 요청에서도 계속 유지해야 하기 때문에 session을 사용했다.

HttpSession session = request.getSession();
int lottoNum = Integer.parseInt(request.getParameter("lottoNum"));
int randomNum = (int)(Math.random() * 6) + 1;
Integer lottoCount = (Integer) session.getAttribute("lottoCount");

if (lottoCount == null) {
    lottoCount = 1;
}

여기서 request.getParameter("lottoNum")는 사용자가 입력한 숫자를 가져오는 부분이고,
session.getAttribute("lottoCount")는 지금까지 몇 번 응모했는지를 가져오는 부분이다.
처음 접속한 사용자는 아직 session 안에 lottoCount가 없기 때문에 null이 나오고, 그 경우 이번 응모를 첫 번째 응모로 보려고 1로 시작하게 했다.

데이터 흐름

HttpSession session = request.getSession();
// 1. 현재 사용자 세션을 가져옴

int lottoNum = Integer.parseInt(request.getParameter("lottoNum"));
// 2. 사용자가 입력한 숫자를 문자열로 받은 뒤 정수로 변환함

int randomNum = (int)(Math.random() * 6) + 1;
// 3. `1~6` 사이 랜덤값을 하나 만듦

Integer lottoCount = (Integer) session.getAttribute("lottoCount");
// 4. 세션에 저장된 응모 횟수를 가져옴

if (lottoCount == null) {
    lottoCount = 1;
}
// 5. 처음 접속이라면 첫 번째 응모로 처리함

→ request는 이번 한 번의 입력값을 가져오는 데 사용된다.
→ session은 여러 번 요청해도 계속 유지되는 응모 횟수를 기억하는 데 사용된다.
→ 이번 단계에서 session을 쓰는 이유가 분명하게 드러난다.



3. 응모 제한, 당첨, 실패 로직 처리

이제 실제 조건 분기 로직을 작성했다.
먼저 응모 횟수가 4 이상인지 확인해서 더 이상 응모할 수 없는 상태인지 먼저 판단했다.
그다음 아직 응모 가능하다면 당첨 여부를 비교해서 각각 다른 메시지, 이미지, 링크 표시 여부를 request에 저장했다.

if(lottoCount >= 4) {
    request.setAttribute("msg", "더 이상 응모할 수 없어요... 브라우저를 재기동한 후에는 가능하답니다.");
    request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/20150415_245/hakdiary_1429085267414cPpEi_JPEG/%BA%B4%B8%C0%C2%A9_%2816%29.jpg?type=w420");
    request.setAttribute("display", "none");
} else if(lottoNum == randomNum) {
    request.setAttribute("msg", "당첨~~~~ 추카추카");
    request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/MjAyMjAxMjBfMzIg/MDAxNjQyNjcxNzgxNzk5.BjVMnEbJ3PAxwZ_HLThbhEZQMB8WDM9dsNJjryRzyUcg._J3HvhZMol90L88A1VG_mrc3RqRvy789Rv1Lil4O7t4g.JPEG.loveletro12/%EC%83%9D%EC%9D%BC_%EC%A7%A4.jpg?type=w800");
    request.setAttribute("display", "none");
    session.setAttribute("lottoCount", lottoCount + 3);
} else {
    request.setAttribute("msg", "실패~~~ 아쉽아쉽");
    request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/MjAyMDEyMDdfMTQ3/MDAxNjA3MzMxMTU4MzIz.im6GMHgcLQ3kg9BatCBj9ESPP4zn1fWGdBcbioVjNsMg.PrhLfZAK_qFHfQA16q9S9d3O3W3-1WH35fT84EMIOjsg.JPEG.nari5636/20140110_232314_610008944.jpg?type=w800");
    request.setAttribute("display", "block");
    session.setAttribute("lottoCount", lottoCount + 1);
}

여기서 중요한 점은 request.setAttribute()와 session.setAttribute()를 서로 다르게 사용했다는 점이다.
msg, imginfo, display는 이번 결과 화면에만 잠깐 필요하므로 request에 저장했다.
반면 lottoCount는 다음 요청에서도 계속 기억해야 하므로 session에 저장했다.


또 이번 로직에서는 실패하면 lottoCount + 1을 저장했고, 당첨이면 lottoCount + 3을 저장했다.
이렇게 하면 당첨이 나왔을 때 다음 요청에서는 바로 lottoCount >= 4 조건에 걸려 더 이상 응모할 수 없게 된다.

데이터 흐름

if(lottoCount >= 4) {
    // 1. 이미 응모 제한 횟수를 넘겼는지 먼저 확인함
    request.setAttribute("msg", "더 이상 응모할 수 없어요... 브라우저를 재기동한 후에는 가능하답니다.");
    request.setAttribute("imginfo", "제한 안내 이미지");
    request.setAttribute("display", "none");
} else if(lottoNum == randomNum) {
    // 2. 아직 응모 가능하고, 번호도 맞은 경우
    request.setAttribute("msg", "당첨~~~~ 추카추카");
    request.setAttribute("imginfo", "당첨 이미지");
    request.setAttribute("display", "none");
    session.setAttribute("lottoCount", lottoCount + 3);
    // 3. 다음 요청부터 바로 막히도록 응모 횟수를 크게 올림
} else {
    // 4. 아직 응모 가능하지만 번호가 틀린 경우
    request.setAttribute("msg", "실패~~~ 아쉽아쉽");
    request.setAttribute("imginfo", "실패 이미지");
    request.setAttribute("display", "block");
    session.setAttribute("lottoCount", lottoCount + 1);
    // 5. 실패했으므로 응모 횟수를 1 증가시킴
}

→ 제한 상태면 결과 비교 없이 바로 제한 메시지를 저장한다.
→ 당첨이면 당첨 메시지와 이미지를 저장하고 다시 돌아가기 링크는 숨긴다.
→ 실패하면 실패 메시지와 이미지를 저장하고 다시 돌아가기 링크는 보이게 한다.
→ 응모 횟수는 session에 저장되기 때문에 다음 요청에서도 이어진다.

트러블 슈팅

처음에는 당첨되면 응모 횟수를 바로 초기화하거나, 정확히 4일 때만 막는 식으로 생각하기 쉬웠다.
그런데 그렇게 하면 어떤 경우에는 횟수 계산이 어긋나거나, 4를 넘어간 상태에서 제한이 제대로 걸리지 않는 문제가 생길 수 있다.
그래서 이번에는 차단 조건을 lottoCount >= 4로 두고, 당첨 시에는 +3, 실패 시에는 +1을 저장하는 방식으로 정리했다.
이렇게 하면 로직 흐름이 단순하고, 다음 요청에서 바로 차단되는 결과도 자연스럽게 만들 수 있다.



4. 같은 JSP로 forward해서 결과 출력하기

마지막에는 저장한 결과값을 같은 JSP로 넘겼다.
이 부분은 앞선 lottoPractice2 실습과 같은 구조다.
즉 페이지를 여러 개로 나누는 게 아니라, 하나의 JSP가 전달받은 값에 따라 화면을 다르게 보여 주는 방식이다.

RequestDispatcher rd = request.getRequestDispatcher("/jspexam/lottoResult2.jsp");
rd.forward(request, response);
<h1><%= request.getAttribute("msg") %></h1>
<img src=<%= request.getAttribute("imginfo") %> />
<a style="display:<%= request.getAttribute("display") %>;" href="/edu/clientexam/lottoForm3.html">다시 돌아가기</a>

이 구조 덕분에 화면 파일은 하나만 써도 된다.
대신 Servlet이 어떤 값을 넘기느냐에 따라 같은 JSP 안에서 결과 화면이 달라진다.
이번 실습에서는 일반 응모 결과와 응모 제한 결과, 이렇게 2가지 화면 형태가 나왔다.

데이터 흐름

RequestDispatcher rd = request.getRequestDispatcher("/jspexam/lottoResult2.jsp");
rd.forward(request, response);
// 1. `request`와 `session`에 담긴 상태를 유지한 채 결과 `JSP`로 넘김
<h1><%= request.getAttribute("msg") %></h1>
<!-- 2. `Servlet`이 저장한 메시지를 제목으로 출력함 -->

<img src=<%= request.getAttribute("imginfo") %> />
<!-- 3. `Servlet`이 저장한 이미지 주소를 꺼내 화면에 보여 줌 -->

<a style="display:<%= request.getAttribute("display") %>;" href="/edu/clientexam/lottoForm3.html">다시 돌아가기</a>
<!-- 4. `display` 값에 따라 링크를 보이거나 숨김 -->

→ Servlet은 데이터를 계산하고 저장한다.
→ JSP는 저장된 값을 꺼내서 화면에 출력한다.
→ 같은 JSP를 쓰더라도 전달되는 값이 달라지면 결과물도 달라진다.



최종 코드

lottoForm3.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>로또 번호를 맞춰 보세요!!!</h1>
    <hr />
    <form method="get" action="/edu/lotto3">
      <label for="lottoNum" style="font-size: 25px">1부터 6까지의 숫자를 입력하세요: </label>
      <input
        type="number"
        id="lottoNum"
        name="lottoNum"
        min="1"
        max="6"
        step="1"
        required
        style="width: 60px; height: 30px; font-size: 18px"
      />
      <input
        type="image"
        src="../images/clover.png"
        alt="로또 추첨"
        style="width: 50px; height: 50px"
      />
    </form>
  </body>
</html>

lottoResult2.jsp

<%@ 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><%= request.getAttribute("msg") %></h1>
<img src=<%= request.getAttribute("imginfo") %> />
<a style="display:<%= request.getAttribute("display") %>;" href="/edu/clientexam/lottoForm3.html">다시 돌아가기</a>
</body>
</html>

LottoServlet3.java

package lab;

import jakarta.servlet.RequestDispatcher;
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 jakarta.servlet.http.HttpSession;
import java.io.IOException;

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

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		 response.setContentType("text/html;charset=utf-8");
		 HttpSession session = request.getSession();
	     int lottoNum = Integer.parseInt(request.getParameter("lottoNum"));
	     int randomNum = (int)(Math.random() * 6) + 1;
	     Integer lottoCount = (Integer) session.getAttribute("lottoCount");

	     if (lottoCount == null) {
	         lottoCount = 1;
	     }

	     System.out.printf("전달된 값: %d", lottoNum);
		 System.out.println();
		 System.out.printf("추출된 값: %d", randomNum);
		 System.out.println();
		 System.out.printf("응모횟수: %d", lottoCount);
		 System.out.println();

	     if(lottoCount >= 4) {
	    	 request.setAttribute("msg", "더 이상 응모할 수 없어요... 브라우저를 재기동한 후에는 가능하답니다.");
	    	 request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/20150415_245/hakdiary_1429085267414cPpEi_JPEG/%BA%B4%B8%C0%C2%A9_%2816%29.jpg?type=w420");
	    	 request.setAttribute("display", "none");
	     } else if(lottoNum == randomNum) {
	    	 request.setAttribute("msg", "당첨~~~~ 추카추카");
	    	 request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/MjAyMjAxMjBfMzIg/MDAxNjQyNjcxNzgxNzk5.BjVMnEbJ3PAxwZ_HLThbhEZQMB8WDM9dsNJjryRzyUcg._J3HvhZMol90L88A1VG_mrc3RqRvy789Rv1Lil4O7t4g.JPEG.loveletro12/%EC%83%9D%EC%9D%BC_%EC%A7%A4.jpg?type=w800");
	    	 request.setAttribute("display", "none");
	    	 session.setAttribute("lottoCount", lottoCount + 3);
	     } else {
	    	 request.setAttribute("msg", "실패~~~ 아쉽아쉽");
	    	 request.setAttribute("imginfo", "https://mblogthumb-phinf.pstatic.net/MjAyMDEyMDdfMTQ3/MDAxNjA3MzMxMTU4MzIz.im6GMHgcLQ3kg9BatCBj9ESPP4zn1fWGdBcbioVjNsMg.PrhLfZAK_qFHfQA16q9S9d3O3W3-1WH35fT84EMIOjsg.JPEG.nari5636/20140110_232314_610008944.jpg?type=w800");
	    	 request.setAttribute("display", "block");
	    	 session.setAttribute("lottoCount", lottoCount + 1);
	     }

	     RequestDispatcher rd = request.getRequestDispatcher("/jspexam/lottoResult2.jsp");
	     rd.forward(request, response);
	}
}



실습하면서 확인한 점

  • 기존 HTML, JSP를 그대로 재사용하고 Servlet 로직만 확장할 수도 있다.
  • 여러 요청 동안 계속 기억해야 하는 값은 request가 아니라 session에 저장해야 한다.
  • request.setAttribute()는 이번 한 번의 결과 화면에만 필요한 값을 넘길 때 사용한다.
  • session.getAttribute()와 session.setAttribute()를 사용하면 응모 횟수처럼 누적되는 값을 관리할 수 있다.
  • 같은 JSP를 사용하더라도 msg, imginfo, display 값을 다르게 넘기면 여러 형태의 결과 화면을 만들 수 있다.
  • 이번 실습에서는 기존 로또 결과 흐름에 session 기반 응모 횟수 제한 조건을 추가하는 방법을 확인할 수 있었다.

0개의 댓글