5. (실습) Servlet으로 HTTP 이해하기

박은서's avatar
Jan 29, 2026
5. (실습) Servlet으로 HTTP 이해하기
notion image

1. lib 폴더 생성

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp

2. View.java (역할만 숙지)

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\lib\View.java

1) View.java 클래스의 역할

💡
서블릿에서 request에 담아둔 데이터를 Mustache 템플릿으로 렌더링해서 HTML 응답으로 보내주는 역할
1️⃣ Controller에서 View를 호출하기 쉽게 해줌
req.setAttribute("name", "Alice"); req.setAttribute("age", 20); View view = new View(template); view.forward(req, resp);
→ HTML 렌더링 책임을 전부 View로 넘김
2️⃣ Request Attribute → Mustache Model 변환
Controller 에서 req.setAttribute("user", user); 를 하면
→ Mustache 템플릿에서 {{user.name}} 로 바로 사용 가능
3️⃣ List / Map 처리 (템플릿 친화적)
  • List → 그대로 반복 가능
  • Map → 키를 최상위 변수처럼 사용 가능
Controller에서 req.setAttribute("data", Map.of("title", "Hello")); 를 하면
→ Mustache 템플릿에서 사용 가능 ( <h1>{{title}}</h1> )
4️⃣ 최종적으로 HTML 응답 생성
resp.setContentType("text/html; charset=UTF-8"); template.execute(model, resp.getWriter());
  • 템플릿 + 모델 → HTML
  • 브라우저로 전송

2) 코드

package com.example.prodwebapp.lib; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import com.samskivert.mustache.Template; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class View { private Template template; public View(Template template) { this.template = template; } public void forward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=UTF-8"); // req에 저장된 모든 attribute를 model로 사용 Map<String, Object> model = new HashMap<>(); Enumeration<String> attributeNames = req.getAttributeNames(); while (attributeNames.hasMoreElements()) { String key = attributeNames.nextElement(); Object value = req.getAttribute(key); // List인 경우 그대로 저장 if (value instanceof List) { model.put(key, value); } // Map인 경우 모든 항목을 개별 키로 추가 else if (value instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) value; model.putAll(map); } // 그 외의 경우 키 이름 그대로 저장 else { model.put(key, value); } } // Mustache 템플릿에서 request 객체에 접근할 수 있도록 추가 model.put("request", req); template.execute(model, resp.getWriter()); resp.getWriter().flush(); } }

3. ViewResolver.java (역할만 숙지)

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\lib\ViewResolver.java

1) ViewResolver.java 클래스의 역할

💡
뷰 이름(String) → Mustache 템플릿 → View 객체로 변환해주는 팩토리(Factory) 역할을 하는 클래스
  • 책임 (SRP 관점)
    • 뷰 이름을 실제 템플릿 파일(.mustache)로 찾아서 View 객체로 만들어 주는 것

2) 코드

package com.example.prodwebapp.lib; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Template; import jakarta.servlet.ServletException; // SRP(단일 책임의 원칙) // templates 폴더에 mustache 파일이 있어서 그걸 연결해주는 친구 public class ViewResolver { // mustache 파일을 읽어서 -> Servlet으로 변경해서 리턴 public static View render(String viewName) throws ServletException, IOException { String resourcePath = "templates/" + viewName + ".mustache"; InputStream in = ViewResolver.class.getClassLoader() .getResourceAsStream(resourcePath); if (in == null) { throw new ServletException("Template not found: " + resourcePath); } try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { Template template = Mustache.compiler().compile(reader); return new View(template); } } }

[참고] View와 ViewResolver의 관계

[참고] View와 ViewResolver의 관계

4. mustache 파일 생성

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\templates
notion image

5. list.mustache

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\templates\list.mustache
<!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> <ul> <li><a href="/product.do?cmd=list">상품목록</a></li> <li><a href="/product.do?cmd=insert-form">상품등록</a></li> </ul> <h1>상품 목록</h1> <hr> <table border="1"> <tr> <td>id</td> <td>name</td> <td>액션</td> </tr> {{#models}} <tr> <td>{{id}}</td> <td><a href="/product.do?cmd=detail&id={{id}}">{{name}}</a></td> <!-- 하이퍼링크는 get 요청 --> <td> <form action="/product.do?cmd=delete&id={{id}}" method="post"> <button type="submit">삭제</button> </form> </td> </tr> {{/models}} </table> </body> </html>

6. detail.mustache

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\templates\detail.mustache
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div { border: 1px solid black; padding: 10px; } </style> </head> <body> <ul> <li><a href="/product.do?cmd=list">상품목록</a></li> <li><a href="/product.do?cmd=insert-form">상품등록</a></li> </ul> <h1>상품 상세보기</h1> <hr> <div> <div>id: {{model.id}}</div> <div>name: {{model.name}}</div> <div>price: {{model.price}}원</div> <div>qty: {{model.qty}}개</div> </div> </body> </html>

7. insert-form.mustache

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\templates\insert-form.mustache
<!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> <ul> <li><a href="/product.do?cmd=list">상품목록</a></li> <li><a href="/product.do?cmd=insert-form">상품등록</a></li> </ul> <h1>상품등록</h1> <hr> <form action="/product.do?cmd=insert" method="post" enctype="application/x-www-form-urlencoded"> 상품명 : <input type="text" name="name" required/> <br/> 가격 : <input type="text" name="price" required/> <br/> 개수 : <input type="text" name="qty" required/> <br/> <button type="submit">상품등록</button> </form> </body> </html>

[참고] Mustache란?

[참고] Mustache란?

8. application.properties

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\application.properties
spring.application.name=prodwebapp spring.output.ansi.enabled=always

9. DBConnection.java

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\DBConnection.java
package com.example.prodwebapp; import java.sql.Connection; import java.sql.DriverManager; public class DBConnection { // 책임 : 데이터베이스 연결 소켓을 리턴함 public static Connection getConnection() { String url = "jdbc:mysql://localhost:3306/productdb"; String username = "root"; String password = "bitc5600!"; try { // new 클래스명(); Class.forName("com.mysql.cj.jdbc.Driver"); // conn = 프로토콜이 적용된 소켓 Connection conn = DriverManager.getConnection(url, username, password); // System.out.println("DB연결 성공"); return conn; } catch (Exception e) { e.printStackTrace(); } return null; } }

10. Product 폴더 생성

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp
notion image

11. Product.java

package com.example.prodwebapp.product; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Product { private Integer id; private String name; private Integer price; private Integer qty; }

12. ProductRepository.java

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\product\ProductRepository.java
package com.example.prodwebapp.product; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.example.prodwebapp.DBConnection; public class ProductRepository { Connection conn = DBConnection.getConnection(); // 1. insert(String name, int price, int qty) public int insert(String name, int price, int qty){ String sql = "insert into product(name, price, qty) values(?,?,?)"; try { // 2. 버퍼달기 PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, name); pstmt.setInt(2, price); pstmt.setInt(3, qty); // 3. 쿼리전송 int result = pstmt.executeUpdate(); return result; } catch (SQLException e) { e.printStackTrace(); } return -1; } // 2. deleteById(int id) public int deleteById(int id){ String sql = "delete from product where id = ?"; try { // 2. 버퍼달기 PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); // 3. 쿼리전송 int result = pstmt.executeUpdate(); return result; } catch (SQLException e) { e.printStackTrace(); } return -1; } // 3. findById(int id) public Product findById(int id){ try { String sql = "select * from product where id=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); // 조회해서 view로 응답받기 ResultSet rs = pstmt.executeQuery(); // select할때!! // 커서 한칸 내리기 boolean isRow = rs.next(); // 행이 존재하면 프로젝션(열 선택하기) if(isRow){ int c1 = rs.getInt("id"); String c2 = rs.getString("name"); int c3 = rs.getInt("price"); int c4 = rs.getInt("qty"); Product product = new Product(c1, c2, c3, c4); return product; } } catch (SQLException e) { e.printStackTrace(); } return null; } // 4. findAll() public List<Product> findAll(){ try { String sql = "select * from product"; PreparedStatement pstmt = conn.prepareStatement(sql); // 조회해서 view로 응답받기 ResultSet rs = pstmt.executeQuery(); // select할때!! // 행이 존재하면 프로젝션(열 선택하기) List<Product> list = new ArrayList<>(); while(rs.next()){ int c1 = rs.getInt("id"); String c2 = rs.getString("name"); int c3 = rs.getInt("price"); int c4 = rs.getInt("qty"); Product product = new Product(c1, c2, c3, c4); list.add(product); } return list; } catch (SQLException e) { e.printStackTrace(); } return Arrays.asList(); } }

13. ProductService.java

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\product\ProductService.java
package com.example.prodwebapp.product; import java.util.List; // 트랜잭션 관리 public class ProductService { ProductRepository repo = new ProductRepository(); public void 상품등록(String name, int price, int qty) { int result = repo.insert(name, price, qty); if(result != 1) throw new RuntimeException("상품등록이 완료되지 않았습니다"); } public List<Product> 상품목록() { return repo.findAll(); } public Product 상품상세(int id) { Product product = repo.findById(id); if(product == null) throw new RuntimeException("상품을 찾을 수 없어요 id를 확인하세요 : 명령어 get"); return product; } public void 상품삭제(int id) { int result = repo.deleteById(id); if(result != 1) throw new RuntimeException("상품삭제가 완료되지 않았습니다"); } }

14. ProductController.java

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\product\ProductController.java
package com.example.prodwebapp.product; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class ProductController { ProductService productService = new ProductService(); public String list(HttpServletRequest req, HttpServletResponse resp) { List<Product> models = productService.상품목록(); req.setAttribute("models", models); return "list"; // view 이름 } public String insertForm(HttpServletRequest req, HttpServletResponse resp) { return "insert-form"; // view 이름 } public String detail(HttpServletRequest req, HttpServletResponse resp) { int id = Integer.parseInt(req.getParameter("id")); Product model = productService.상품상세(id); req.setAttribute("model", model); return "detail"; // view 이름 } public String insert(HttpServletRequest req, HttpServletResponse resp) { String name = req.getParameter("name"); int price = Integer.parseInt(req.getParameter("price")); int qty = Integer.parseInt(req.getParameter("qty")); productService.상품등록(name, price, qty); return "/product.do?cmd=list"; // 주소 url (get이 아니라 action 요청이 들어오면 viewName이 아니라 무조건 url 리턴) } public String delete(HttpServletRequest req, HttpServletResponse resp) { int id = Integer.parseInt(req.getParameter("id")); productService.상품삭제(id); return "/product.do?cmd=list"; } }

15. DispatcherServlet.java

C:\workspace\spring_lab\servlet-product-exam\src\main\java\com\example\prodwebapp\DispatcherServlet.java

1) 코드

package com.example.prodwebapp; import java.io.IOException; import com.example.prodwebapp.lib.View; import com.example.prodwebapp.lib.ViewResolver; import com.example.prodwebapp.product.ProductController; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @WebServlet("*.do") public class DispatcherServlet extends HttpServlet { ProductController pc = new ProductController(); // localhost:8080/product.do?cmd=list @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 라우팅 String cmd = req.getParameter("cmd"); if ("list".equals(cmd)) { String viewName = pc.list(req, resp); ViewResolver.render(viewName).forward(req, resp); } else if ("insert-form".equals(cmd)) { String viewName = pc.insertForm(req, resp); ViewResolver.render(viewName).forward(req, resp); } else if ("detail".equals(cmd)) { String viewName = pc.detail(req, resp); ViewResolver.render(viewName).forward(req, resp); } } // localhost:8080/product.do?cmd=insert // localhost:8080/product.do?cmd=delete&id=6 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 라우팅 String cmd = req.getParameter("cmd"); if ("insert".equals(cmd)) { // url = /product.do?cmd=list String url = pc.insert(req, resp); // resp.sendRedirect(url); // 리다이렉션(재요청) 아래 2개의 코드를 위의 sendRedirect가 한 번에 처리해 줌 resp.setStatus(302); resp.setHeader("location", url); } else if ("delete".equals(cmd)) { String url = pc.delete(req, resp); resp.sendRedirect(url); } } }

[참고] request 객체와 response 객체

[참고] request 객체와 response 객체

[참고] 포워딩(forward)

[참고] 포워딩(forward)

[참고] 리다이렉트(Redirect)

[참고] 리다이렉트(Redirect)

[참고] HTTP 상태 코드(Status)

[참고] HTTP 상태 코드(Status)

[참고] POST 요청 - Body와 Header 구조

[참고] POST 요청 - Body와 Header 구조

16. index.html

C:\workspace\spring_lab\servlet-product-exam\src\main\resources\static\index.html

1) index.html 파일의 역할

  • 웹사이트의 기본 시작 페이지(엔트리 포인트) 역할

2) 코드

<script> location.href = "/product.do?cmd=list"; </script>

17. main

package com.example.prodwebapp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.server.servlet.context.ServletComponentScan; @ServletComponentScan @SpringBootApplication public class ProdwebappApplication { public static void main(String[] args) { SpringApplication.run(ProdwebappApplication.class, args); } }

18. 결과

메인화면
메인화면
상품 상세 보기 (상품 목록에서 고구마 클릭)
상품 상세 보기 (상품 목록에서 고구마 클릭)
상품 등록 화면
상품 등록 화면
딸기 등록됨 → 삭제 클릭
딸기 등록됨 → 삭제 클릭
딸기 삭제됨
딸기 삭제됨
 
Share article