반응형
*이전 글과 이어집니다.
스프링 인 액션 제5판(크레이그 월즈 지음) - 웹 애플리케이션 개발하기 1
*책 내용과 살짝 다르게, 다음과 같은 환경에서 프로젝트를 생성2026.05.25 - [스프링(Spring), 스프링부트(SpringBoot)/스프링(Spring) 기초] - 스프링 인 액션 제5판(크레이그 월즈 지음) - 스프링 시작하기
keep-programming-study.tistory.com
2. 폼 제출 처리하기
- 뷰 페이지 html의 <form> 태그에 action 속성을 "post"로 설정하면,
- 브라우저가 폼의 모든 데이터를 모아서 폼에 나타난 GET 요청과 같은 "/design" 경로로 서버에 HTTP POST 요청을 전송할 수 있게 함
1) @PostMapping을 사용해서 POST 요청 처리
(1) DesignTacoController에 메서드 추가
package tacos.web;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
@Slf4j // Lombok: 로그 출력을 위한 Slf4j 로거 자동 생성
@Controller // Spring MVC 컨트롤러로 등록
@RequestMapping("/design") // "/design" URL 요청을 처리하는 컨트롤러
public class DesignTacoController {
...
// 사용자가 폼에서 입력한 타코 디자인 데이터를 받아옴
// Taco 객체에는 타코 이름(name)과 선택된 재료(ingredients)가 바인딩됨
@PostMapping
public String processDesign(Taco design) {
// 이 지점에서 타코 디자인(선택된 속재료)을 저장
log.info("속재료 저장:" + design);
// 추후 DB 저장 코드 추가
// 타코 디자인을 처리한 후 주문 페이지로 리다이렉트
// "/orders/current" URL로 이동하여 주문 진행 화면을 보여줌
return "redirect:/orders/current";
}
}
(2) OrderController 생성
package tacos.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Order;
@Slf4j // Lombok: Slf4j 로거 자동 생성 → 로그 출력 가능
@Controller // Spring MVC 컨트롤러로 등록
@RequestMapping("/orders") // "/orders" URL 요청을 처리하는 컨트롤러
public class OrderController {
// "/orders/current" 경로로 들어오는 GET 요청을 처리
@GetMapping("/current")
public String orderForm(Model model) {
// 새로운 Order 객체를 생성해서 모델에 추가
// 뷰(orderForm.html)에서 폼 바인딩에 사용됨
model.addAttribute("order", new Order());
// "orderForm" 뷰 이름 반환 → templates/orderForm.html 렌더링
return "orderForm";
}
// "/orders" 경로로 들어오는 POST 요청 처리
@PostMapping
public String processOrder(Order order) {
// 사용자가 주문 폼을 제출하면 Order 객체에 데이터가 바인딩됨
// Order에는 이름, 주소, 결제 정보 등 주문 관련 필드가 들어 있음
// 현재는 단순히 제출된 주문 내용을 로그로 출력
// 추후에는 DB에 저장하거나 주문 처리 로직을 추가할 예정
log.info("Order submitted: " + order);
// 주문을 처리한 후 홈 화면("/")으로 리다이렉트
// 즉, 주문 완료 후 메인 페이지로 이동시킴
return "redirect:/";
}
}
2) 타코 주문 폼 뷰(view) 생성
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xthml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Taco Cloud - Order</title>
<link rel="stylesheet" th:href="@{style.css}"/>
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png"/>
<a th:href="@{/design}" id="another">Design another taco</a><br />
<!-- 필드 오류 발생 시 나타남 -->
<div th:if="${#fields.hasErrors()}">
<span class="validationError">
Please correct the problems blow and resubmit.
</span>
</div>
<!-- 배송자 이름, 배송 주소 및 우편번호 입력 -->
<h3>Deliver my taco masterpieces to...</h3>
<label for="deliveryName">Name: </label>
<input type="text" th:field="*{deliveryName}" />
<br />
<label for="deliveryStreet">Street address: </label>
<input type="text" th:field="*{deliveryStreet}" />
<br />
<label for="deliveryCity">City: </label>
<input type="text" th:field="*{deliveryCity}">
<br />
<label for="deliveryState">State: </label>
<input type="text" th:field="*{deliveryState}">
<br />
<!-- 배송 우편번호 입력 -->
<label for="deliveryZip">Zip code: </label>
<input type="text" th:field="*{deliveryZip}">
<br />
<!-- 결제수단 입력(카드) -->
<h3>Here's how I'll pay</h3>
<!-- 카드 번호 -->
<label for="ccNumber">Credit Card: </label>
<input type="text" th:field="*{ccNumber}">
<br />
<!-- 카드 유효기간 입력 -->
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpirattion}"/>
<br />
<!-- 카드 CVV 3자리 입력 -->
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}"/>
<br />
<!-- 주문 양식 제출 버튼 -->
<input type="submit" value="Sumbit order" />
</form>
</body>
</html>
3) 주문 정보를 저장할 Order 클래스 생성
package tacos;
import lombok.Data;
@Data // Lombok: getter/setter, toString, equals, hashCode 자동 생성
public class Order {
// 배송자 이름
private String deliveryName;
// 배송 거리(Street)
private String deliveryStreet;
// 배송 도시(City)
private String deliveryCity;
// 배송 주(State)
private String deliveryState;
// 배송 우편번호(Zip)
private String deliveryZip;
// 결제용 신용카드 번호
private String ccNumber;
// 신용카드 유효기간 (MM/YY 형식)
private String ccExpiration;
// 신용카드 보안코드(CVV)
private String ccCVV;
}
3. 폼 입력 유효성 검사하기
- 자바의 Bean Validation API를 통한 Bean 유효성 검사로 쉽게 규칙을 선언
1) 유효성 검사 규칙 선언하기
(1) pom.xml에 의존성 추가
- 코드 수정 후, 프로젝트 우클릭 → Maven → Update Project 실행
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
(2) Taco 도메인 클래스에 유효성 검사 규칙 추가
package tacos;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
// 타코(Taco) 디자인을 정의하는 도메인 클래스
@Data // Lombok: getter/setter, toString, equals, hashCode 자동 생성
public class Taco {
// 타코 이름 (예: "Carnitas Taco", "Veggie Taco")
@NotNull // 빈 값이 들어가는 것을 방지
@Size(min=5, message="Name must be at least 5 characters long") // 문자열, 배열, 컬렉션, Map 모두 길이/크기 검증: 5글자 이상
private String name;
// 타코에 들어가는 재료 목록
// 여기서는 재료 ID(String) 리스트로 관리
@Size(min=1, message="You must choose at least 1 ingredient") // 문자열, 배열, 컬렉션, Map 모두 길이/크기 검증: 리스트 길이가 1 이상ㄴ
private List<String> ingredients;
}
(3) Order 클래스에 유효성 검사 규칙 추가
package tacos;
import org.hibernate.validator.constraints.CreditCardNumber;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Data // Lombok: getter/setter, toString, equals, hashCode 자동 생성
public class Order {
// 배송자 이름
@NotBlank(message="Name is required")
private String deliveryName;
// 배송 거리(Street)
// 값이 null이거나 공백("")이면 에러 발생
@NotBlank(message="Street is required")
private String deliveryStreet;
// 배송 도시(City)
// 값이 null이거나 공백("")이면 에러 발생
@NotBlank(message="City is required")
private String deliveryCity;
// 배송 주(State)
// 값이 null이거나 공백("")이면 에러 발생
@NotBlank(message="State is required")
private String deliveryState;
// 배송 우편번호(Zip)
// 값이 null이거나 공백("")이면 에러 발생
@NotBlank(message="Zip code is required")
private String deliveryZip;
// 결제용 신용카드 번호
// Luhn 알고리즘을 이용해 카드번호 유효성 검사
// -> Luhn 알고리즘: 신용카드 번호 같은 식별 번호가 올바르게 입력되었는지 확인하기 위한 단순한 체크섬 공식으로, 카드가 실제로 존재하거나 잔액이 있는지 확인하지는 못함
// => 체크섬: 데이터가 전송되거나 저장되는 과정에서 오류가 발생했는지 확인하기 위한 검증 값
@CreditCardNumber(message="Not a valid credit card number")
private String ccNumber;
// 신용카드 유효기간 (MM/YY 형식)
// @Pattern: 정규식을 이용해 형식 검사
// ^(0[1-9]|1[0-2]) → 월은 01~12
// (\\/([1-9][0-9])) → 슬래시 뒤에 두 자리 연도
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/]([1-9][0-9])$", message="Must be formatted MM/YY")
private String ccExpiration;
// 신용카드 보안코드(CVV)
// @Digits: 숫자만 허용, 정수 3자리, 소수점 없음
@Digits(integer=3, fraction=0, message="Invalid CVV")
private String ccCVV;
}
(4) 폼과 바인딩될 때 유효성 검사 수행하기
1. DesignTacoController.java 수정 - 제출한 Taco의 유효성 검사
package tacos.web;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
@Slf4j // Lombok: 로그 출력을 위한 Slf4j 로거 자동 생성
@Controller // Spring MVC 컨트롤러로 등록
@RequestMapping("/design") // "/design" URL 요청을 처리하는 컨트롤러
public class DesignTacoController {
... 생략 ...
// 사용자가 폼에서 입력한 타코 디자인 데이터를 받아옴
// Taco 객체에는 타코 이름(name)과 선택된 재료(ingredients)가 바인딩됨
@PostMapping
public String processDesign(@Valid Taco design, Errors errors) {
// Taco 유효성 검사를 실행하여(@Valid) 에러 발생 시 Taco 디자인 저장 처리를 중지하고 "design" 뷰를 반환
if (errors.hasErrors()) {
return "design";
}
// 이 지점에서 타코 디자인(선택된 속재료)을 저장
log.info("속재료 저장:" + design);
// 추후 DB 저장 코드 추가
// 타코 디자인을 처리한 후 주문 페이지로 리다이렉트
// "/orders/current" URL로 이동하여 주문 진행 화면을 보여줌
return "redirect:/orders/current";
}
}
2. OrderController.java 수정 - 제출한 Order의 유효성 검사
package tacos.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import tacos.Order;
import tacos.Taco;
@Slf4j // Lombok: Slf4j 로거 자동 생성 → 로그 출력 가능
@Controller // Spring MVC 컨트롤러로 등록
@RequestMapping("/orders") // "/orders" URL 요청을 처리하는 컨트롤러
public class OrderController {
... 생략 ...
// "/orders" 경로로 들어오는 POST 요청 처리
@PostMapping
public String processOrder(@Valid Order order, Errors errors) {
// 사용자가 주문 폼을 제출하면 Order 객체에 데이터가 바인딩됨
// Order에는 이름, 주소, 결제 정보 등 주문 관련 필드가 들어 있음
// Order의 유효성 검사를 실행하여(@Valid) 에러 발생 시 Order 처리를 중지하고 "orderForm" 뷰를 반환
if (errors.hasErrors()) {
return "orderForm";
}
// 현재는 단순히 제출된 주문 내용을 로그로 출력
// 추후에는 DB에 저장하거나 주문 처리 로직을 추가할 예정
log.info("Order submitted: " + order);
// 주문을 처리한 후 홈 화면("/")으로 리다이렉트
// 즉, 주문 완료 후 메인 페이지로 이동시킴
return "redirect:/";
}
}
(5) 유효성 검사 에러 보여주기 - orderForm.html 수정 (모든 필드에 <span>을 추가)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xthml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Taco Cloud - Order</title>
<link rel="stylesheet" th:href="@{style.css}"/>
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png"/>
<a th:href="@{/design}" id="another">Design another taco</a><br />
<!-- 필드 오류 발생 시 나타남 -->
<div th:if="${#fields.hasErrors()}">
<span class="validationError">
Please correct the problems blow and resubmit.
</span>
</div>
<!-- 배송자 이름, 배송 주소 및 우편번호 입력 -->
<h3>Deliver my taco masterpieces to...</h3>
<label for="deliveryName">Name: </label>
<input type="text" th:field="*{deliveryName}" />
<!-- 배송자 이름 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('deliveryName')}"
th:errors="*{deleveryName}">
Name Error
</span>
<br />
<label for="deliveryStreet">Street address: </label>
<input type="text" th:field="*{deliveryStreet}" />
<!-- 배송 거리 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('deliveryStreet')}"
th:errors="*{deleveryStreet}">
Street Error
</span>
<br />
<label for="deliveryCity">City: </label>
<input type="text" th:field="*{deliveryCity}">
<!-- 배송 도시 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('deliveryCity')}"
th:errors="*{deleveryCity}">
City Error
</span>
<br />
<label for="deliveryState">State: </label>
<input type="text" th:field="*{deliveryState}">
<!-- 배송 주 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('deliveryState')}"
th:errors="*{deleveryState}">
State Error
</span>
<br />
<!-- 배송 우편번호 입력 -->
<label for="deliveryZip">Zip code: </label>
<input type="text" th:field="*{deliveryZip}">
<!-- 배송 우편번호 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('deliveryZip')}"
th:errors="*{deleveryZip}">
Zip Error
</span>
<br />
<!-- 결제수단 입력(카드) -->
<h3>Here's how I'll pay</h3>
<!-- 카드 번호 -->
<label for="ccNumber">Credit Card: </label>
<input type="text" th:field="*{ccNumber}">
<!-- 카드 번호 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('ccNumber')}"
th:errors="*{ccNumber}">
CC Num Error
</span>
<br />
<!-- 카드 유효기간 입력 -->
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpiration}"/>
<!-- 카드 유효기간 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('ccExpiration')}"
th:errors="*{ccExpiration}">
CC Expiration Error
</span>
<br />
<!-- 카드 CVV 3자리 입력 -->
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}"/>
<!-- 배송자 이름 유효성 검사 오류 -->
<span class="validationError"
th:if="${#fields.hasErrors('ccCVV')}"
th:errors="*{ccCVV}">
CC CVV Error
</span>
<br />
<!-- 주문 양식 제출 버튼 -->
<input type="submit" value="Sumbit order" />
</form>
</body>
</html>
반응형
'스프링(Spring), 스프링부트(SpringBoot) > 스프링(Spring) 기초' 카테고리의 다른 글
| 스프링 인 액션 제5판(크레이그 월즈 지음) - 웹 애플리케이션 개발하기 1 (0) | 2026.07.19 |
|---|---|
| 스프링 인 액션 제5판(크레이그 월즈 지음) - 스프링 살펴보기 (0) | 2026.06.19 |
| 스프링 인 액션 제5판(크레이그 월즈 지음) - 스프링 시작하기 2 (1) | 2026.06.10 |
| 스프링 인 액션 제5판(크레이그 월즈 지음) - 스프링 시작하기 1 (0) | 2026.05.25 |
| Spring Interceptors(스프링 인터셉터) (0) | 2023.01.02 |