폼 등록용 객체를 생성할 때, Bean Validation에서 제공하는 어노테이션을 사용하여 값을 검증하도록 한다.
UserForm
package hello.board.controller;
import lombok.Getter;
import javax.validation.constraints.NotBlank;
@Getter
public class UserForm {
@NotBlank
private String loginId;
@NotBlank
private String password;
@NotBlank
private String name;
private Integer age;
}
Integer
타입으로 선언한다.@NotBlank
: 빈값이거나 공백만 있는 경우를 허용하지 않는다.Bean Validation 이란?
Bean Validation은 특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다.
쉽게 이야기해서 검증 애노테이션과 여러 인터페이스의 모음이다. 마치 JPA가 표준 기술이고 그 구현체로 하이버네이트가 있는 것과 같다.
Bean Validation을 구현한 기술 중에 일반적으로 사용하는 구현체는 하이버네이트 Validator이다. 이름이 하이버네이트가 붙어서 그렇지 ORM과는 관련이 없다.
하이버네이트 Validator 관련 링크
참고: [Spring Boot] @NotNull, @NotEmpty, @NotBlank 의 차이점 및 사용법
@NotNull
: null을 허용하지 않음@NotEmpty
: null, "" 둘 다 허용하지 않음@NotBlank
: null, "", " " 모두 허용하지 않음
UserController
package hello.board.controller;
import hello.board.entity.User;
import hello.board.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@Controller
@Slf4j
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/signup")
public String signupForm(@ModelAttribute UserForm userForm) {
log.info("signupForm");
return "users/signupForm";
}
@PostMapping("/signup")
public String signup(@Valid @ModelAttribute UserForm userForm, BindingResult bindingResult) {
log.info("signup");
if (bindingResult.hasErrors()) {
log.info("errors = {}", bindingResult);
return "users/signupForm";
}
User user = User.builder()
.loginId(userForm.getLoginId())
.password(userForm.getPassword())
.name(userForm.getName())
.age(userForm.getAge()).build();
userService.join(user);
log.info("signup success");
return "redirect:/";
}
}
BindingResult
를 사용하여 스프링이 제공하는 검증 오류 처리 방법을 사용한다.BindingResult 주의점
BindingResult bindingResult
파라미터의 위치는 @ModelAttribute UserForm userForm
다음에 와야 한다. BindingResult가 UserForm 객체의 바인딩 결과를 담기 때문이다.templates/users/signupForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>회원 가입</h2>
</div>
<h4 class="mb-3">회원 정보 입력</h4>
<form action="" th:action th:object="${userForm}" method="post">
<div>
<label for="loginId">ID</label>
<input type="text" id="loginId" th:field="*{loginId}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{loginId}" />
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" id="password" th:field="*{password}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{password}" />
</div>
<div>
<label for="name">이름</label>
<input type="text" id="name" th:field="*{name}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{name}" />
</div>
<div>
<label for="age">나이</label>
<input type="text" id="age" th:field="*{age}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{age}" />
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">회원
가입</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
th:onclick="|location.href='@{/}'|"
type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
th:field="*{loginId}"
th:field
는, 정상 상황에는 모델 객체의 값을 사용하지만, 이 필드에 오류가 발생하면 BindingResult
에서 보관한 값을 사용해서 값을 출력(value)한다.th:field의 기능
- id, name, value 속성 자동 생성
th:errorclass
와 같이 사용될 경우,th:field
값에 에러가 있으면 class 속성에 errorclass 값을 추가한다.th:field
값에 오류가 생기면, 정상 상황에는 모델 객체의 값을 사용하지만, 이 필드에 오류가 발생하면 FieldError 에서 보관한 값을 사용해서 값을 출력(value)한다.
th:field
는 input 태그에 적용 하는 필드다.
th:errors
가 실행된다. 만약 이때 오류가 있다면 생성된 오류 메시지 코드를 순서대로 돌아가면서 메시지를 찾는다. 그리고 없으면 디폴트 메시지를 출력한다.타임리프 스프링 검증 오류 통합 기능
타임리프는 스프링의BindingResult
를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.
#fields
:#fields
로BindingResult
가 제공하는 검증 오류에 접근할 수 있다.
ex)th:if="${#fields.hasGlobalErrors()}"
th:if="${#fields.hasErrors('loginId')}"
th:errors
: 해당 필드에 오류가 있는 경우에 태그를 출력한다.th:if
의 편의 버전이다. 오류가 있다면 생성된 오류 메시지 코드를 순서대로 돌아가면서 메시지를 찾는다. 그리고 없으면 디폴트 메시지를 출력한다.
- 에러가 없으면 출력X
th:errorclass
:th:field
에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.
어디에서 문제가 생겼는지 알아보기 위해 UserController
에서 로그를 찍는다.
@PostMapping("/signup")
public String signup(@Valid @ModelAttribute("userForm") UserForm userForm, BindingResult bindingResult) {
log.info("signup");
if (bindingResult.hasErrors()) {
log.info("errors = {}", bindingResult);
return "users/signupForm";
}
log.info("signup success");
...
}
hello.board.controller.UserController : errors = org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'userForm' on field 'loginId': rejected value [null]; codes [NotBlank.userForm.loginId,...
Field error in object 'userForm' on field 'password': rejected value [null]; codes [NotBlank.userForm.password,...
Field error in object 'userForm' on field 'name': rejected value [null]; codes [NotBlank.userForm.name,...
HttpServletRequest
를 추가한 다음 getParameter
메서드를 사용하여 값을 받아오는지 확인한다.String loginId = request.getParameter("loginId");
System.out.println(loginId);
HttpServletRequest
로 가져오면 값이 나타난다.UserForm
에 @Setter
가 없었기 때문에 값이 바인딩되지 않았다.@ModelAttribute
는 Setter를 통해 객체에 값을 바인딩하는데, Setter 메서드가 없어서 값을 바인딩하지 못했다.스프링MVC는
@ModelAttribute
가 있으면 다음을 실행한다.
- UserForm 객체를 생성한다.
- 요청 파라미터의 이름으로 UserForm 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
- 예) 파라미터 이름이
loginId
이면setloginId()
메서드를 찾아서 호출하면서 값을 입력한다.
UserForm
수정 (@Setter 추가)package hello.board.controller;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
@Getter @Setter
public class UserForm {
@NotBlank
private String loginId;
@NotBlank
private String password;
@NotBlank
private String name;
private Integer age;
}
DB에도 회원 가입이 반영 된 것을 확인할 수 있다.
나이는 숫자만 입력해야 한다. 다른 타입을 입력한다면 오류 메시지가 출력되는데, 스프링에서 기본으로 설정한 오류 메시지 대신 직접 오류 메시지를 설정한다.
application.properties
수정
spring.messages.basename=messages,errors
errors.properties
추가
typeMismatch.java.lang.Integer=숫자를 입력해주세요