반응형
1. Validation(검증) 이란?
- 올바르지 않은 데이터(타입에러, 범위에러 등)가 입력되었을 때 걸러내는 작업
- validation을 할 때 Client Side 뿐아닌 Server Side에서의 validation도 중요
- validation이 없다면 입력을 마친 상품 등록 폼을 Controller에서 전송 받고 이 정보들로 별도의 검증 없이 바로 상품 객체로 만들어 등록 후 다음화면으로 이동시킴
- validation이 있다면 입력을 마친 상품 등록 폼이 Controller에 전송 되었을 때 Controller에서 이 입력값들에 대한 validation을 진행함
- validation을 통과해야 객체 생성, 등록 후 다음 화면으로 이동
- validation을 통과하지 못하면 전에 사용자의 입력 + 에러메세지를 들고 다시 상품 등록 폼으로 이동시킴
2. @Valid를 활용한 Validation 진행 예제
- 상품을 등록하는 예제를 통해 Validation을 진행해 봄
- 사용자가 상품을 등록할 때, 아래와 같은 Validation 조건들이 있음
- 이름 : 비어있으면 안되고, 최대 10글자까지 허용 (필드 에러)
- 판매자 이메일 : 비어있으면 안되고, 이메일 형식이여야 함 (필드 에러)
- 가격 : 비어있으면 안되고, 100원 이상 1,000,000원 이하여야 함 (필드 에러)
- 수량 : 비어있으면 안되고, 최대 999개까지 허용 (필드 에러)
- 추가적으로 가격 * 수량이 500,000,000원을 넘으면 안됨 (글로벌 에러)
2.1. spring-boot-starter-validation 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
2.2. Item 객체
- Item 객체
@Data
@AllArgsConstructor
public class Item {
private int id;
private String name;
private String sellerEmail;
private int price;
private int quantity;
}
2.3. 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를 따로 설정해 줄 수도 있음
2.4. validation 관련 어노테이션들이 정리되어 있는 사이트
- 위에서 사용한 어노테이션들 외에도 여러가지 어노테이션들이 정리되어 있는 사이트
- https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec
2.5. 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로 돌아가게 됨
- 이 때, 전에 사용자가 입력했던 값들은 자동으로 같이 넘어감
2.6. 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>
2.7. 결과
- 기본 페이지
- 필드 에러 발생 1
- 필드 에러 발생 2
- 글로벌 에러 발생
3. 에러 메세지 설정의 필요성
- 글로벌 에러와 name에 관한 에러 메세지들은 직접 작성한 에러 메세지가 출력되었음
- 이를 제외한 메세지들은 (ex) 공백일 수 없습니다) 모두 스프링에서 자동으로 출력해주는 메세지들임
- name과 같이 에러 메세지를 모두 작성해 줄 수 있지만 에러 메세지들이 너무 많아진다면 일일이 작성하기 어려움
- 따라서 에러 메세지들을 한번에 설정, 관리하는 방법이 필요
4. 에러 메세지 관리 방법
- 프로젝트의 main/resources 폴더 안에 'errors.properties' 파일 생성
- application.properties에 다음 코드 추가
spring.messages.basename = errors
- 이제 error.properties에 각각의 에러에 맡게 원하는 메세지들을 넣어주면 됨
5. 에러 메세지 작성 방법
5.1. Global Error
- Global Error은 errorCode = errorMessage 형식으로 작성하면 됨
- 위에서 Global Error Code를 overMaxTotalPrice로 지정
5.2. Field Error
- Field Error에는 우선순위가 존재함
- Field Error 우선 순위 (아래로 갈수록 우선순위가 낮아짐)
- NotBlank.addItemForm.name = 상품명은 필수입니다!
- NotBlank.name = 이름은 필수입니다!
- NotBlank.java.lang.String = 빈 문자열 일 수 없습니다!
- NotBlank = 공백일 수 없습니다!
- 위와 같이 코드를 작성했다면
- addItemForm의 name이 빈 칸이라면 "상품명은 필수입니다!"가 출력됨
- addItemForm이 아닌 다른 객체의 name이 빈 칸이라면 "이름은 필수입니다!"가 출력됨
- addItemFrom의 email이 빈 칸이라면 String이기 때문에 "빈 문자열 일 수 없습니다!"가 출력됨
- String 타입도 아닌 필드가 빈 칸이라면 "공백일 수 없습니다!"가 출력됨
5.3. 필드명, parameter 출력
- {0} : 필드명, {1}, {2}, ..은 파라미터를 의미
- price 필드에 @Range(min = 100, max = 1000000) validation을 걸어줬음
- 이 값들이 화면에 출력되고 싶다면 {} 활용
- "Range.item.price = {0}는 {2}원 ~ {1}원 사이 값이여야 합니다!" 와 같이 작성해주면 "price는 100원 ~ 1,000,000원 사이 값이여야 합니다!"로 출력됨
5.4. Type Error
- Type Error는 typeMismatch 사용
- TypeError 우선 순위 (아래로 갈수록 우선순위가 낮아짐)
- typeMismatch.addItemForm.price = 가격은 숫자여야 합니다!
- typeMismatch.java.lang.Integer = 숫자가 입력되야 합니다!
- typeMismatch = 입력타입이 잘못되었습니다!
- price, quantity 필드에 문자를 넣었다면 price는 "가격은 숫자여야 합니다!"을 출력하고 quantity는 "숫자가 입력되야 합니다!"을 출력함
- 이 특성들을 이용해 에러 메세지를 세분화 할 수 있고, 범용성 있게 쓸 수도 있음
5.5. 최종 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 = 입력타입이 잘못되었습니다!
5.6. 결과
- 글로벌 에러 발생시키는 코드를 아래와 같이 수정 후 실행
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 |
↓ 클릭시 이동
1. Validation(검증) 이란?2. @Valid를 활용한 Validation 진행 예제2.1. spring-boot-starter-validation 라이브러리 추가2.2. Item 객체2.3. AddItemForm 객체 생성2.4. validation 관련 어노테이션들이 정리되어 있는 사이트2.5. ItemController2.6. addForm.html2.7. 결과3. 에러 메세지 설정의 필요성4. 에러 메세지 관리 방법5. 에러 메세지 작성 방법5.1. Global Error5.2. Field Error5.3. 필드명, parameter 출력5.4. Type Error5.5. 최종 errors.properties5.6. 결과