Contents
1. lib 폴더 생성2. View.java (역할만 숙지)3. ViewResolver.java (역할만 숙지)[참고] View와 ViewResolver의 관계4. mustache 파일 생성5. list.mustache6. detail.mustache7. insert-form.mustache[참고] Mustache란?8. application.properties9. DBConnection.java10. Product 폴더 생성11. Product.java12. ProductRepository.java13. ProductService.java14. ProductController.java15. DispatcherServlet.java16. index.html17. main18. 결과
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

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

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