반응형

 

도서 'Spring in Action' 제 5판을 보고 책 내용과 그 이외의 부족한 부분을 채워가며 공부한 내용입니다.

 


키워드 : POST, Controller, 유효성 검사


1. 폼 제출 처리하기

이전의 design.html의 <form>태그

<form method="POST" th:object="${taco}">

method 속성 = POST

이에대한 action 속성이 선언되어 있지 않음.

  • 브라우저가 폼의 모든 데이터를 모아서 폼에 나타난 GET 요청과 같은 경로(/design)로 서버에 HTTP POST 요청을 전송

 

/design 경로의 POST 요청을 처리하는 새로운 메서드 필요

@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {

	@GetMapping
	public String showDesignFrom(Model model) {
		List<Ingredient> ingredients = Arrays.asList(
				new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
				new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
				new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
				new Ingredient("CARN", "Carnitas", Type.PROTEIN),
				new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
				new Ingredient("LETC", "Lettuce", Type.VEGGIES),
				new Ingredient("CHED", "Cheddar", Type.CHEESE),
				new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
				new Ingredient("SLSA", "Salsa", Type.SAUCE),
				new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
				);
				
				Type[] types = Ingredient.Type.values();
				for(Type type : types) {
					model.addAttribute(type.toString().toLowerCase(),
							filterByType(ingredients, type));
				}
				
				model.addAttribute("taco", new Taco());
				
				return "design";
	}
	
	private List<Ingredient> filterByType(
			List<Ingredient> ingredients, Type type){
		return ingredients
				.stream()
				.filter(x -> x.getType().equals(type))
				.collect(Collectors.toList());
	}
	
	@PostMapping
	public String processDesign(@Valid Taco design, Errors errors) {
		if (errors.hasErrors()) {
			return "design";
		}
		
		// 이 지점에서 타코 디자인(선택된 식자재 내역)을 저장한다…
		// 이 작업은 3장에서 할 것이다.
		log.info("Processing design: " + design);
		return "redirect:/orders/current";
	}
	
}

<코드 분석>

  • public String processDesign(@Valid Taco design, Errors errors)

반환값 = String -> 사용자에게 보여주는 View(redirection : 변경된 경로로 재접속) 

인자로 Taco 객체가 들어옴.(이전에 미리 코딩해둔 Taco class)

 

※ Taco.java

package tacos;

import java.util.List;
import lombok.Data;

@Data
public class Taco {
	private String name;
	private List<String> ingredients;
}

 

※ 현재 Taco에 대한 처리는 더이상 하지 않고 3장 진행시 로직을 추가할 예정

 

desing/orders 에 들어온 요청을 처리하기 위한 Controller 만들기

@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
	
	@GetMapping("/current")
	public String orderForm(Model model) {
		model.addAtribute("order", new Order());
		return "orderForm";
	}
}

<코드 분석>

  • public String orderForm(Model model)

orderForm을 반환

 

 

orderForm.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	  xmlns:th="http://www.thymeleaf.org">
  <head>
	<meta charset="EUC-KR">
	<title>Taco Cloud</title>
	<link rel="stylesheet" th:href="@{/styles.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
				below 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="*{deliveryName}">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="*{deliveryStreet}">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="*{deliveryCity}">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="*{deliveryState}">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="*{deliveryZip}">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 Num Error</span>
		<br /> 
		
		<label for="ccCVV">CVV: </label> 
		<input type="text" th:field="*{ccCVV}" /> 
		<span class="validationError"
			th:if="${#fields.hasErrors('ccCVV')}"
			th:errors="*{ccCVV}">CC Num Error</span>
		<br />
		
		<input type="submit" value="Submit order" />
	</form>
  </body>
</html>

<코드 분석>

  • <form method="POST" th:action="@{/orders}" th:object="${order}">

action도 지정하고 있음

/orders 경로로 제출되도록 지정

(지정되어있지 않다면 design.html처럼 폼에 나타났던 것과 같은 URL로 HTTP POST요청이 제출)

 

 

기존의 OrderController에 PostMapping 추가

public class OrderController{
	...
    
    @PostMapping
	public String processOrder(@Valid Order order, Errors errors) {
		if (errors.hasErrors()) {
			return "orderForm";
		}
		
		log.info("Order submitted: " + order);
		return "redirect:/";
	}
}

<코드 분석>

  • public String processOrder(@Valid Order order, Errors errors)

제출된 폼 빌드와 바인딩된 속성을 갖는 Order 객체가 인자로 전달

 

 

폼 입력 유효성 검사 API가 포함된 Order class

package tacos;

import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.CreditCardNumber;

import lombok.Data;

@Data
public class Order {
	
	@NotBlank(message="Name is required")
	private String deliveryName;
	
	@NotBlank(message="Street is required")
	private String deliveryStreet;
	
	@NotBlank(message="City is required")
	private String deliveryCity;
	
	@NotBlank(message="State is required")
	private String deliveryState;
	
	@NotBlank(message="Zip code is required")
	private String deliveryZip;
	
	@CreditCardNumber(message="Not a valid credit card number")
	private String ccNumber;
	
	@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
			message="Must be formatted MM/YY")
	private String ccExpiration;
	
	@Digits(integer=3, fraction=0, message="Invalid CVV")
	private String ccCVV;
}
  • 스프링 -> 자바의 Bean 유효성 검사(Bean Validation API(JSR-303; https://jcp.org/en/jsr/detail?id=303)를 지원
  • 추가 코드 작성 없이 입력된 값의 유효성 검사 규칙을 쉽게 선언 가능

 

2. 유효성 검사

  1. 유효성을 검사할 Class(여기서는 Taco와 Order)에 검사 규칙을 선언
  2. 유효성 검사를 해야하는 컨트롤러 메서드에 검사를 수행한다는 것을 지정
  3. 검사 에러를 보여주도록 폼 View를 수정

※ 유효성 검사 API를 구현한 Hibernate 컴포넌트에는 더 많은 유효성 검사 어노테이션이 추가되었음.

 

Taco class 유효성 검사

@Data
public class Taco {
	
	@NotNull
	@Size(min=5, message="Name must be at least 5 characters long")
	private String name;
	
	@Size(min=1, message="You must choose at least 1 ingredient")
	private List<String> ingredients;
}

<코드 분석>

@NotNull : 해당 속성에 값이 없거나 null인지 확인

@Size(min=5, message="~") : 최소한 5문자 이상이어야 하고 오류시 해당 message 출력

 

Order class 유효성 검사

@Data
public class Order {
	
	@NotBlank(message="Name is required")
	private String deliveryName;
	
	@NotBlank(message="Street is required")
	private String deliveryStreet;
	
	@NotBlank(message="City is required")
	private String deliveryCity;
	
	@NotBlank(message="State is required")
	private String deliveryState;
	
	@NotBlank(message="Zip code is required")
	private String deliveryZip;
	
	@CreditCardNumber(message="Not a valid credit card number")
	private String ccNumber;
	
	@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
			message="Must be formatted MM/YY")
	private String ccExpiration;
	
	@Digits(integer=3, fraction=0, message="Invalid CVV")
	private String ccCVV;
}

<코드 분석>

@NotBlank(message="~") : 해당 필드가 입력을 했는지 체크(빈칸이라면 해당 message 출력)

@CreditCardNumber(message="~") : 해당 속성의 값이 Lune 알고리즘 검사에 합격한 유효한 신용카드 번호여야함.

@Pattern(정규 표현식) : 해당 정규 표현식을 따르는지 확인

@Digits(integer=3, fraction=0, message="~") : 입력값이 정확히 3자리 숫자인지 체크

 

3. 유효성 검사 에러 보여주기

fields, th:errors 속성 사용

orderForm.html

...
		<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 />
        
....

<코드 분석>

  • <span class="validationError"
    th:if="${#fields.hasErrors('ccNumber')}"
    th:errors="*{ccNumber}">CC Num Error</span>

class = 사용자의 주의를 끌기 위한 에러의 명칭을 지정하는데 사용

th:if = 해당 span을 보여줄지 말지 결정(error.hasErrors()를 사용해 해당 필드에(여기서는 ccNumber) 에러 체크)

th:error = ccNumber의 필드 참조, 이 필드가 에러가 있다면 CC Num Error를 검사 에러 메세지(message)로 변경


참고 자료

1. 크레이그 월즈, Spring in Action, Fifth Edition(출판지 : 제이펍, 2020)

반응형

'Spring' 카테고리의 다른 글

Spring 데이터로 작업하기 1  (0) 2022.05.18
Spring 웹 애플리케이션 개발 3  (0) 2022.05.11
Spring 웹 애플리케이션 개발 1  (0) 2022.05.11
Spring 기초 - 2  (0) 2022.05.09
Spring 기초 - 1  (0) 2022.05.09

+ Recent posts