반응형
Validation(검증) 이란?
- 올바르지 않은 데이터(타입에러, 범위에러 등)가 입력되었을 때 걸러내는 작업
- validation을 할 때 Client Side 뿐아닌 Server Side에서의 validation도 중요
- validation이 없다면 입력을 마친 상품 등록 폼을 Controller에서 전송 받고 이 정보들로 별도의 검증 없이 바로 상품 객체로 만들어 등록 후 다음화면으로 이동시킴
- validation이 있다면 입력을 마친 상품 등록 폼이 Controller에 전송 되었을 때 Controller에서 이 입력값들에 대한 validation을 진행함
- validation을 통과해야 객체 생성, 등록 후 다음 화면으로 이동
- validation을 통과하지 못하면 전에 사용자의 입력 + 에러메세지를 들고 다시 상품 등록 폼으로 이동시킴
@Valid를 활용한 Validation 진행 예제
- 상품을 등록하는 예제를 통해 Validation을 진행해 봄
- 사용자가 상품을 등록할 때, 아래와 같은 Validation 조건들이 있음
- 이름 : 비어있으면 안되고, 최대 10글자까지 허용 (필드 에러)
- 판매자 이메일 : 비어있으면 안되고, 이메일 형식이여야 함 (필드 에러)
- 가격 : 비어있으면 안되고, 100원 이상 1,000,000원 이하여야 함 (필드 에러)
- 수량 : 비어있으면 안되고, 최대 999개까지 허용 (필드 에러)
- 추가적으로 가격 * 수량이 500,000,000원을 넘으면 안됨 (글로벌 에러)
spring-boot-starter-validation 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
Item 객체
- Item 객체
@Data
@AllArgsConstructor
public class Item {
private int id;
private String name;
private String sellerEmail;
private int price;
private int quantity;
}
AddItemForm 객체 생성
- AddItemForm 객체
- 입력을 Item 객체로 받는것이 아닌 AddItemForm 객체로 받음
- Item 객체 자체로 입력받지 않고 따로 입력용 객체를 만들어 입력받았을 때의 장점
- AddItemForm 객체에는 Item 객체의 모든 변수가 있을 필요 없이 원하는 값만 받을 수 있음
- 오직 이 페이지에서 입력을 받기 위한 객체이므로 더 직관적임
- 다른 페이지에서도 Item을 생성할 수 있는데 이런 경우와 구분 가능
- AddItemForm에 validation annotation들을 넣어줌으로써 이 페이지의 들어오는 입력에 대한 validation만을 따로 지정할 수 있음
@Data
public class AddItemForm {
@NotBlank(message = "상품명이 비어있을 수 없습니다!")
@Length(max = 10, message = "상품명의 최대 길이는 10글자 입니다!")
private String name;
@NotBlank
@Email
private String sellerEmail;
@NotNull
@Range(min = 100, max = 1000000)
private int price;
@NotNull
@Max(999)
private int quantity;
}
- 위에서 적어두었던 필드 에러들을 모두 어노테이션을 사용해 validation 진행 예정
- error message를 따로 설정해 줄 수도 있음
validation 관련 어노테이션들이 정리되어 있는 사이트
- 위에서 사용한 어노테이션들 외에도 여러가지 어노테이션들이 정리되어 있는 사이트
- https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec
ItemController
@Slf4j
@Controller
@RequestMapping("/validation")
public class ItemController {
@GetMapping("/add")
public String addItemForm(Model model) {
model.addAttribute("addItemForm",new AddItemForm());
return "validation_practice/addForm";
}
@PostMapping("/add")
public String addItem(@Valid @ModelAttribute("addItemForm") AddItemForm form, BindingResult bindingResult) {
// 글로벌 에러 처리
Long totalPrice = Long.valueOf(form.getPrice() * form.getQuantity());
if(totalPrice > 500000000) {
bindingResult.reject("overMaxTotalPrice",
"글로벌 에러 발생 : 가격 * 수량이 5억이 넘을 수 없습니다. 입력값 : " + totalPrice);
}
// 모든 에러 처리
if(bindingResult.hasErrors()) {
log.info("상품 등록 실패");
return "validation_practice/addForm";
}
log.info("상품 등록 완료");
return "redirect:/validation/add";
}
}
- GetMapping 에서 model에 Item이 아닌 AddItemForm을 담아줘서 전송
- PostMapping 에서는 @ModelAttribute를 사용해 입력을 받음
- 먼저 가격 * 수량이 5억이 넘으면 안된다는 글로벌 에러를 처리해줌
- 글로벌 에러 발생 시, bindingResult에 error을 추가시켜 아래의 조건문에서 걸리게 함
- @Valid 어노테이션이 필드 에러들을 검증해서 조건이 맞지 않다면 error을 발생시킴
- 필드 에러, 글로벌 에러 중 하나라도 존재한다면 조건문에 걸리게 되고 다시 addForm.html로 돌아가게 됨
- 이 때, 전에 사용자가 입력했던 값들은 자동으로 같이 넘어감
addForm.html
- th:errors는 th:field로 연결된 변수에 field-error가 발생하면 해당 태그를 나타나게끔 처리
- th:errorclass는 th:field로 연결된 변수에 에러가 발생한다면 해당 태그에 "error-class"라는 클래스를 추가시켜줌
- error-class는 별다른 의미는 없고 마지막에 style을 통해 테두리와 글자색을 빨간색으로 적용시켜주기 위해 사용
- 필드 에러는 th:field를 사용해 별도의 추가 코드 없이 간편하게 사용했다면 글로벌 에러는 마지막 div와 같이 직접 작성해줘야 함
<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>상품 추가 Form</h1>
<form action="/validation/add" th:object="${addItemForm}" method="post" style="width: 400px">
<div>
<label th:for="name">상품명 : </label>
<input type="text" th:field="*{name}" placeholder="상품명을 입력하세요"
th:errorclass="error-class">
<div class="error-class" th:errors="*{name}"></div>
</div> <br/>
<div>
<label th:for="sellerEmail">이메일 : </label>
<input type="text" th:field="*{sellerEmail}" placeholder="판매자 이메일을 입력하세요"
th:errorclass="error-class">
<div class="error-class" th:errors="*{sellerEmail}"></div>
</div> <br/>
<div>
<label th:for="price">가격 : </label>
<input type="text" th:field="*{price}" th:errorclass="error-class">
<div class="error-class" th:errors="*{price}"></div>
</div> <br/>
<div>
<label th:for="quantity">수량 : </label>
<input type="text" th:field="*{quantity}" th:errorclass="error-class">
<div class="error-class" th:errors="*{quantity}"></div>
</div>
<!-- 글로벌 에러 처리 부분 -->
<div th:if="${#fields.hasGlobalErrors()}">
<p class="error-class" th:each="err : ${#fields.globalErrors()}" th:text="${err}"/>
</div> <br/>
<button type="submit">상품 등록</button>
</form>
</body>
</html>
<style>
input {
width: 200px;
}
.error-class {
color: red;
border-color: red;
}
</style>
결과
- 기본 페이지
- 필드 에러 발생 1
- 필드 에러 발생 2
- 글로벌 에러 발생
에러 메세지 설정의 필요성
- 글로벌 에러와 name에 관한 에러 메세지들은 직접 작성한 에러 메세지가 출력되었음
- 이를 제외한 메세지들은 (ex) 공백일 수 없습니다) 모두 스프링에서 자동으로 출력해주는 메세지들임
- name과 같이 에러 메세지를 모두 작성해 줄 수 있지만 에러 메세지들이 너무 많아진다면 일일이 작성하기 어려움
- 따라서 에러 메세지들을 한번에 설정, 관리하는 방법이 필요
에러 메세지 관리 방법
- 프로젝트의 main/resources 폴더 안에 'errors.properties' 파일 생성
- application.properties에 다음 코드 추가
spring.messages.basename = errors
- 이제 error.properties에 각각의 에러에 맡게 원하는 메세지들을 넣어주면 됨
에러 메세지 작성 방법
Global Error
- Global Error은 errorCode = errorMessage 형식으로 작성하면 됨
- 위에서 Global Error Code를 overMaxTotalPrice로 지정
Field Error
- Field Error에는 우선순위가 존재함
- Field Error 우선 순위 (아래로 갈수록 우선순위가 낮아짐)
- NotBlank.addItemForm.name = 상품명은 필수입니다!
- NotBlank.name = 이름은 필수입니다!
- NotBlank.java.lang.String = 빈 문자열 일 수 없습니다!
- NotBlank = 공백일 수 없습니다!
- 위와 같이 코드를 작성했다면
- addItemForm의 name이 빈 칸이라면 "상품명은 필수입니다!"가 출력됨
- addItemForm이 아닌 다른 객체의 name이 빈 칸이라면 "이름은 필수입니다!"가 출력됨
- addItemFrom의 email이 빈 칸이라면 String이기 때문에 "빈 문자열 일 수 없습니다!"가 출력됨
- String 타입도 아닌 필드가 빈 칸이라면 "공백일 수 없습니다!"가 출력됨
필드명, parameter 출력
- {0} : 필드명, {1}, {2}, ..은 파라미터를 의미
- price 필드에 @Range(min = 100, max = 1000000) validation을 걸어줬음
- 이 값들이 화면에 출력되고 싶다면 {} 활용
- "Range.item.price = {0}는 {2}원 ~ {1}원 사이 값이여야 합니다!" 와 같이 작성해주면 "price는 100원 ~ 1,000,000원 사이 값이여야 합니다!"로 출력됨
Type Error
- Type Error는 typeMismatch 사용
- TypeError 우선 순위 (아래로 갈수록 우선순위가 낮아짐)
- typeMismatch.addItemForm.price = 가격은 숫자여야 합니다!
- typeMismatch.java.lang.Integer = 숫자가 입력되야 합니다!
- typeMismatch = 입력타입이 잘못되었습니다!
- price, quantity 필드에 문자를 넣었다면 price는 "가격은 숫자여야 합니다!"을 출력하고 quantity는 "숫자가 입력되야 합니다!"을 출력함
- 이 특성들을 이용해 에러 메세지를 세분화 할 수 있고, 범용성 있게 쓸 수도 있음
최종 errors.properties
# Field Error
NotBlank.item.name = 상품명은 필수입니다!
NotNull.item.price = 가격을 입력해주세요!
Range.item.price = {0}는 {2}원 ~ {1}원 사이 값이여야 합니다!
Email.item.sellerEmail = 판매자 이메일이 이메일 형식이 아닙니다!
Email = Email 형식이 아닙니다!
NotNull = {0}은 공백일 수 없습니다!
NotBlank = {0}은 공백일 수 없습니다!
Range = {2} ~ {1} 허용
Max = 최대 {1}
# Global Error
overMaxTotalPrice = 상품 가격 * 수량의 최대값은 500,000,000 입니다! 현재 값은 {1} 입니다!
# Type Error
typeMismatch.item.price = 가격은 숫자여야 합니다!
typeMismatch.java.lang.Integer = 숫자가 입력되야 합니다!
typeMismatch = 입력타입이 잘못되었습니다!
결과
- 글로벌 에러 발생시키는 코드를 아래와 같이 수정 후 실행
bindingResult.reject("overMaxTotalPrice", new Object[]{500000000, totalPrice}, "글로벌 에러 메세지");
- 필드 에러 메세지 적용 결과 1
- 필드 에러 메세지 적용 결과 2
- 글로벌 에러 메세지 적용 결과
반응형
'Spring Boot > 문법 정리' 카테고리의 다른 글
[Spring Boot] Converter & Formatter (0) | 2022.06.28 |
---|---|
[Spring Boot] Filter & Interceptor (0) | 2022.06.23 |
[Spring Boot] Thymeleaf - 메세지, 국제화 (0) | 2022.06.10 |
[Spring Boot] Thymeleaf - form 관련 기능 정리 (1) | 2022.06.09 |
[Spring Boot] Thymeleaf 기능 정리 (0) | 2022.06.04 |