유효성 검사는 사용자가 입력한 ㄷ데이터가 올바른 형식인지 검사하는 것을 의미한다.
유효성 검사는 단일항목 검사, 상관항목 검사로 나뉜다.
단일 항목 검사: 입력 항목 하나 하나에 대해 입력을 체크한다.
이를 위해서는 Form클래스 등 필드에 에너테이션을 부여한다.
주로, 자바 Bean validation 또는, 하이버네이트 프레임워크의 Hibernate Validator 를 사용한다.
상관항목검사 : 입력 필드 1개가 아닌, 여러필드를 조합해서 입력을 체크한다.
주로, 3가지 방법을 사용한다.
| 에너테이션 | 설명 | 사용예시 |
|---|---|---|
| @NotNull | 값이 Null인지 확인한다. | @NotNull private String name |
| @NotEmpty | 문자열, 컬렉션이 비어있는지 확인 | @NotEmpty private String name |
| @NotBlank | 문자열이 비어있는지 확인 | @NotBlank private String name |
| @Min | 수치가 지정된 최솟값 이상인지 체크 | @Min(19) private int age |
| @Max | 수치가 지정된 최대값 이하인지 체크 | @Max(100) private String name |
| @Size | 문자열, 배열, 컬렉션 크기가 지정범위 내에 있는지 확인 | @Size(min=1, max=10) private String name |
| @Pattern | 문자열이 지정된 표현식과 일치하는지 확인 | @Pattern(regexp="^[a-zA-Z]+$") private String name |
| 문자열이 이메일 형식인지 확인 | @Email private String email | |
| @Positive | 숫자가 양수인지 확인 | @Positive private int value |
| @PositiveOrZero | 숫자가 양수 또는 0인지 확인 | @Positive private int value |
| @Negative | 숫자가 음수인지 확인 | @Negative private int value |
| @NegativeOrZero | 숫자가 음수 또는 0인지 확인 | @NegativeOrZero private int value |
| 에너테이션 | 설명 | 사용예시 |
|---|---|---|
| @Length | 문자열크기가 지정범위 내에 있는지 확인 | @Length(min=1, max=10) private String name |
| @Range | 수치가 지정된 범위 내에 있는지 확인 | @Range(min=1, max=10) private int value |
| @CreditCardNumber | 유효한 신용카드 번호인지 확인 | @CreditCardNumber private String creditCard |
이 외에도 다양한 입력값 검사 에너테이션이 존재한다.
| 에너테이션 | 설명 |
|---|---|
| @NotNull | null일 경우 오류, (빈 문자, 공백 허용) |
| @NotEmpty | null일 경우 오류, (빈 문자 오류, 공백 허용) |
| @NotBlank | null일 경우 오류, (빈 문자 오류, 공백 오류) |
의존성 추가
1. Spring Boot DevTools
2. Thymeleaf
3. Spring Web
4. Lombok
5. Validation(입출력 유효성 검사 의존성)
빌드 세팅
1. 설정-빌드도구-gradle-gradle jvm에서 java버전 변경하기.
2. 프로젝트 구조- 프로젝트설정-프로젝트SDK에서 java버전 변경하기.
폼 클래스란? 웹페이지(뷰)에서 사용자에게 데이터를 전송하기 위한 입력양식 처리에 사용되는 클래스로, 주로 컨트롤러에서 이 클래스를 이용한다.
@Data
public class CalcForm {
@NotNull(message = "왼쪽: 숫자가 입력되지 않았습니다.")
@Range(min = 1, max = 10, message = "왼쪽: {min}~{max} 범위의 숫자를 입력하세요.")
private Integer leftNum;
@NotNull(message = "오른쪽: 숫자가 입력되지 않았습니다.")
@Range(min = 1, max = 10, message = "오른쪽: {min}~{max} 범위의 숫자를 입력하세요.")
private Integer rightNum;
}
#### 해석
@Data: 자동으로 getter, setter 등 생성해준다.
leftNum, rightNum 두개의 변수는 각가 두개의 입력값 검사를 거친다.
각각 1~10 사이어야 한다.
@Controller
public class ValidationController {
/** '폼 연동 빈' 초기화 */
@ModelAttribute
public CalcForm setUpForm() {
return new CalcForm();
}
/** 입력 화면 표시 */
@GetMapping("show")
public String showView() {
// 반환값으로 뷰 이름을 반환
return "entry";
}
}
#### 해석
폼 연동 빈 초기화란?
@ModelAttribute와 return값으로, Form 클래스를 선언한 함수를 만들면 되며
입력 페이지가 열렸을 때, 컨트롤러에서 폼과 연동할 객체(Calc Form)을 미리 만들어 모델에 저장해 놓았다가,
출력 뷰에 전달하는 역할을 한다. (단, 폼 연동 빈은 일시적 저장이다.)
왜 이렇게 하는가?
기존방식은 데이터를 입력에 성공했을 땐 문제가없지만, 성공하지 못하면 객체가 만들어지지 않아,
입력값검사나, 에러 메시지를 자동으로 보여줄 수 없다.
이렇게 입력 페이지가 열리자 마자 CalcForm객체를 모델에 저장해둔다면, 편하게 객체검증이 가능하다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>입력 화면</title>
</head>
<body>
<form th:action="@{/calc}" method="post" th:object="${calcForm}">
<div>
<input type="text" th:field="*{leftNum}">
+
<input type="text" th:field="*{rightNum}">
</div>
<input type="submit" value="계산">
</form>
</body>
</html>
#### 해석
th:action="@{/calc}" method="post" : 이 폼은 /calc URL로 post방식으로 보낸다.
th:object="${calcForm} : 이 폼은 calcForm객체와 바인딩 됨을 선언한다.
@Controller
public class ValidationController {
/** '폼 연동 빈' 초기화 */
@ModelAttribute
public CalcForm setUpForm() {
return new CalcForm();
}
/** 입력 화면 표시 */
@GetMapping("show")
public String showView() {
// 반환값으로 뷰 이름을 반환
return "entry";
}
// ▽▽▽▽▽ 예제 8.4 ▽▽▽▽▽
/** 확인 화면 표시: Form 클래스 사용 */
@PostMapping("calc")
public String confirmView(@Validated CalcForm form,
BindingResult bindingResult, Model model) {
// 입력 체크
if (bindingResult.hasErrors()) { //에러를 가지고 있다면,
// 입력 오류
// 입력 화면으로 되돌아간다.
return "entry";
}
// 입력 오류 없음
// 덧셈 실행
Integer calcResult = form.getLeftNum() + form.getRightNum();
// Model에 저장
model.addAttribute("result", calcResult);
// 확인 화면으로 이동
return "confirm";
}
// △△△△△ 예제 8.4 △△△△△
}
#### 해석
첫 컨트롤러에 예제 8.4를 추가하였다.
@PostMapping("calc"): calc URL로 데이터가 Post방식으로 들어왔을 때,
@Validated CalcForm form : 입력뷰에서, th:object="${calcForm} 를 통해 폼의 데이터가
CalcForm 객체로 바인딩되고, @Validated를 통해 입력값 검사 조건이 적용된다.
BindingResult bindingResult: 검증 결과가 이 객체에 저장된다.
bindingResult.hasError() : 를 통해 에러를 검사받는다.
model.addAttribute("result", calcResult) : 더한 결과를 담은 객체를 result라는 모델명으로
모델에 추가한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>입력 화면</title>
</head>
<body>
<form th:action="@{/calc}" method="post" th:object="${calcForm}">
<div>
<input type="text" th:field="*{leftNum}">
+
<input type="text" th:field="*{rightNum}">
</div>
<input type="submit" value="계산">
<!-- ▽▽▽▽▽ 예제 8.5 ▽▽▽▽▽ -->
<!-- 오류 표시 -->
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="err:${#fields.errors('*')}" th:text="${err}"></li>
</ul>
<!-- △△△△△ 예제 8.5 △△△△△ -->
</form>
</body>
</html>
#### 해석
ul th:if= : 만약, 어떤 경우라도, 에러가 있으면 ul이 보여진다.
th:each="err:${#fields.errors('*')} : 모든 에러사항을 출력 하기 위한 반복문이다.
#fields는 타입리프가 제공하는 필드와 관련된 도움을 주는 객체이다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>확인 화면</title>
</head>
<body>
<h2>계산 결과</h2>
<h3>[[${calcForm.leftNum}]]+[[${calcForm.rightNum}]]=[[${result}]]</h3>
</body>
</html>
#### 해석
모델에 저장된 데이터를 각각 출력한다.
calc.Form(leftNum,rightNum) + result 출력,
결과( 정상 범위 값을 입력)


결과(오류 범위 값을 입력)

입력 체크 함수(bindingResult.hasErrors()) 결과가 return Entry 임으로, 입력 화면(Entry)로 돌아간다.
@ModelAttribute가 포함된 에너테이션은 요청 메서드가 호출되기 전에 자동으로 실행된다.
@Controller
public class MyController {
@ModelAttribute("message")
public String setupMessage() {
return "Hello, World!";
}
@GetMapping("/greet")
public String greet() {
return "greeting-page";
}
}
여기서, GetMapping전에, message라는 이름으로, return결과인 Hello, World가 모델에 저장된다.
만약 메서드의 인수에 이 에너테이션을 사용하면, 요청파라미터에서 자동으로 바인딩된 객체를 얻는다.
@Controller
public class MyController {
@RequestMapping("/result")
public String submitForm(@ModelAttribute("userForm") User user) {
// 폼에서 전송된 데이터는 User 객체에 바인딩됨
System.out.println("사용자명: " + user.getUserName());
return "result-page";
}
}
쉽게 말하자면, 요청 파라미터와, User 클래스의 필드명이 일치하면, Spring이 자동으로 User객체를 만들어줘 필드에 값을 자동으로 넣어준다. (=자동 바인딩)
따라서, 컨트롤러의 User 객체에 값이 자동으로 담기게 된다.
1. 요청 파라미터 = (Username, age)
<form action="/result" method="post">
<input name="userName" type="text">
<input name="age" type="text">
<button type="submit">제출</button>
</form>
2. User 클래스 구성
public class User {
private String userName;
private int age;
// getter, setter
}
3. 컨트롤러
@Controller
public class MyController {
@RequestMapping("/result")
public String submitForm(@ModelAttribute("userForm") User user) {
// 폼에서 전송된 데이터는 User 객체에 바인딩됨
System.out.println("사용자명: " + user.getUserName());
return "result-page";
}
}
#### 만약 자동 바인딩을 사용하지 않았을 경우 아래처럼 파라미터를 직접 받아야한다.
@RequestMapping("/result")
public String submitForm(@RequestParam("userName") String userName,
@RequestParam("age") int age) {
User user = new User();
user.setUserName(userName);
user.setAge(age);
// 이후 user 객체 사용
return "result-page";
}
이러한 특성 때문에 Form(입력 양식 저장한 태그, 여러개 데이터를 한번에 주고 받음)을 사용할 때 주로 사용한다.
Spring은 클래스의 요청핸들러 메소드에 사용자 정의 데이터 타입(User 클래스 등)이 있는 경우 자동으로 해당타입의 객체를 생성하고, 요청 매개변수를 바인딩한다. 따라서, 아래처럼도 작동한다.
@Controller
public class MyController {
@RequestMapping("/result")
public String submitForm(User user) {
// 폼에서 전송된 데이터는 User 객체에 바인딩됨
System.out.println("사용자 이름: " + user.getUserName());
return "result-page";
}
}
다만, 이 경우, 클래스 첫문자가 소문자로 저장되어 모델명이 user가 된다.
의존성 추가
1. Spring Boot DevTools
2. Thymeleaf
3. Spring Web
4. Lombok
5. Validation(입출력 유효성 검사 의존성)
빌드 세팅
1. 설정-빌드도구-gradle-gradle jvm에서 java버전 변경하기.
2. 프로젝트 구조- 프로젝트설정-프로젝트SDK에서 java버전 변경하기.
@Data
public class SampleForm {
/** 비밀번호 */
private String password;
/** 확인용 비밀번호 */
private String confirmPassword;
// 비밀번호와 확인용 비밀번호가 일치하는지 확인
@AssertTrue(message = "비밀번호가 일치하지 않습니다")
public boolean isSamePassword() {
return Objects.equals(password, confirmPassword);
}
}
#### 해석
@Data: Getter Setter를 자동으로 만들어준다.
상관항목 검사를 하기 위해 반드시 @AssertTrue 애너테이션과 is로 시작하는 클래스를 만들어야한다.
@AssertTrue는 is, get, set등을 뺀 남은 부분의 첫글자를 소문자로 바꾼뒤 이것을
변수명으로 만들고, 이렇게 만들어진 변수명으로 에러를 처리한다.
즉, samePassword 변수가
@AssertTrue(message = "비밀번호가 일치하지 않습니다") : bool 타입을 리턴하는 메소드와
같이 사용하며,
true면 통과, false면 에러가 발생하고, 그 에러 메세지를 지정한 문구로 바꾼다.
@Controller
public class CheckController {
// 입력 화면 표시
@GetMapping()
public String showForm(SampleForm form) {
return "entry";
}
// 상관 항목 검사 실행
@PostMapping
public String check(@Validated SampleForm form,
BindingResult bindingResult, Model model) {
// 유효성 검사 수행
if (bindingResult.hasErrors()) {
return "entry";
}
model.addAttribute("message", "입력에 문제가 없습니다");
return "result";
}
}
#### 해석
showForm에서 @Validated는 생략형이 되어,
SampleForm 타입의 객체를 동적 바인딩 하여 인수로 받는다.
따라서, 모델명은 sampleForm이 된다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>상관 항목 검사</title>
</head>
<body>
<h1>@AssertTrue 활용</h1>
<form th:action="@{/}" th:object="${sampleForm}" method="post">
<p>비밀번호 <input type="password" th:field="*{password}" /></p>
<p>확인용 비밀번호 <input type="password" th:field="*{confirmPassword}" /></p>
<!-- 상관 항목 검사 오류 메시지 -->
<p th:if="${#fields.hasErrors('samePassword')}"
th:errors="*{samePassword}" style="color: red;">
상관 항목 검사 오류
</p>
<p><input type="submit" value="확인" /></p>
</form>
</body>
</html>
#### 해석
<form th:action="@{/}" th:object="${sampleForm}" method="post"> :sampleForm 모델(생략형 앞글자 소문자)
을 가져와, post방식으로 전송한다.
th:if="${#fields.hasErrors('samePassword')}" : #fields는 타입리프가 제공하는 필드관련 객체
.hasError('samePassword'): fields.samePassword라는 필드에 검증에러가 있는지 체크한다.
만약 있다면,
해당 필드에서 발생한 오류메시지를 th:error=*{samePassword}를 통해서 출력한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>상관 항목 검사</title>
</head>
<body>
<h1 th:text="${message}">메시지</h1>
</body>
</html>


