Spring Validation
Spring Validation์ ์ฌ์ฉ์ ์
๋ ฅ ๋ฐ์ดํฐ์ ์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ชป๋ ๋ฐ์ดํฐ๊ฐ ์ฒ๋ฆฌ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ณ , ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๋๋ฐ ๋์์ ์ค๋๋ค.
์ฃผ์ ํน์ง
โข
Bean Validation API๋ฅผ ์ฌ์ฉํ ์ฌ์ด ์ ํจ์ฑ ๊ฒ์ฆ
โข
๋ค์ํ ๋ด์ฅ ๊ฒ์ฆ ์ด๋
ธํ
์ด์
์ ๊ณต
โข
์ปค์คํ
๊ฒ์ฆ ๋ก์ง ๊ตฌํ ๊ฐ๋ฅ
โข
์ปจํธ๋กค๋ฌ์์ ์๋ ๊ฒ์ฆ ์ฒ๋ฆฌ
โข
์ค๋ฅ ๋ฉ์์ง ์ปค์คํฐ๋ง์ด์ง ์ง์
์ด ๊ฐ์์์๋ Spring๊ณผ MyBatis๋ฅผ ์ด์ฉํ์ฌ user ํ
์ด๋ธ ๊ธฐ๋ฐ ํ์๊ฐ์
๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ , Validation์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ํ์ตํฉ๋๋ค.
Validation ์ฃผ์ ์ด๋
ธํ
์ด์
์ด๋
ธํ
์ด์
| ์ค๋ช
|
@NotNull | null ๊ฐ ๋ถํ |
@NotBlank | null, ๋น ๋ฌธ์์ด, ๊ณต๋ฐฑ๋ง์ผ๋ก ์ด๋ฃจ์ด์ง ๋ฌธ์์ด ๋ถํ |
@Size | ๋ฌธ์์ด, ๋ฐฐ์ด์ ๊ธธ์ด ๊ฒ์ฆ (min, max ์์ฑ) |
@Email | ์ด๋ฉ์ผ ํ์ ๊ฒ์ฆ |
@Past | ๊ณผ๊ฑฐ ๋ ์ง๋ง ํ์ฉ |
@Future | ๋ฏธ๋ ๋ ์ง๋ง ํ์ฉ |
@Valid | ์ค์ฒฉ๋ ๊ฐ์ฒด์ ์ ํจ์ฑ ๊ฒ์ฌ ์คํ |
BidingResult ์ฃผ์ ๋ฉ์๋
๋ฉ์๋ | ์ค๋ช
|
hasErrors() | ์ด๋ค ์ข
๋ฅ๋ ์ค๋ฅ๊ฐ ์๋์ง ํ์ธ |
hasGlobalErrors() | ๊ฐ์ฒด ๋ ๋ฒจ์ ์ค๋ฅ๊ฐ ์๋์ง ํ์ธ |
hasFieldErrors() | ํ๋ ๋ ๋ฒจ์ ์ค๋ฅ๊ฐ ์๋์ง ํ์ธ |
getFieldError() | ํน์ ํ๋์ ์ฒซ ๋ฒ์งธ ์ค๋ฅ ๋ฉ์์ง ๋ฐํ |
getAllErrors() | ๋ชจ๋ ์ค๋ฅ ๋ชฉ๋ก ๋ฐํ |
DB ํ
์ด๋ธ ์ ์
CREATE TABLE `user` (
`no` INT AUTO_INCREMENT PRIMARY KEY,
`id` VARCHAR(64) UNIQUE,
`username` VARCHAR(100) UNIQUE,
`password` VARCHAR(100) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`email` VARCHAR(100) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
SQL
๋ณต์ฌ
DTO + Validation ์ด๋
ธํ
์ด์
์ด๋
ธํ
์ด์
| ์ค๋ช
|
@NotBlank | null ๋ฐ ๊ณต๋ฐฑ ๋ถํ |
@Size | ๊ธธ์ด ์ ์ฝ ์ค์ |
@Email | ์ด๋ฉ์ผ ํ์ ๊ฒ์ฆ |
build.gradle
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '3.5.9'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.aloha'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(23)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.5'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Java
๋ณต์ฌ
User.java
@Data
public class Users {
private Integer no;
@Size(min = 4, max = 64, message = "์์ด๋๋ 4์ ์ด์ 64์ ์ดํ๋ก ์
๋ ฅํ์ธ์.")
private String id;
@NotBlank(message = "์์ด๋๋ ํ์์
๋๋ค.")
@Pattern(
regexp = "^[A-Za-z0-9]{6,20}$",
message = "์์ด๋๋ ์๋ฌธ๊ณผ ์ซ์๋ง ๊ฐ๋ฅํ๋ฉฐ 6~20์ ์ด๋ด๋ก ์์ฑํด์ผํฉ๋๋ค."
)
private String username;
@NotBlank(message = "๋น๋ฐ๋ฒํธ๋ ํ์์
๋๋ค.")
@Pattern(
regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$",
message = "๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด๋ฉฐ, ์๋ฌธ, ์ซ์, ํน์๋ฌธ์๋ฅผ ํฌํจํด์ผํฉ๋๋ค."
)
private String password;
@NotBlank(message = "์ด๋ฆ์ ํ์์
๋๋ค.")
@Size(min = 2, max = 10, message = "์ด๋ฆ์ 2~10๊ธ์ ์ด๋ด๋ก ์์ฑํด์ผํฉ๋๋ค.")
private String name;
@Email(message = "์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
@NotBlank(message = "์ด๋ฉ์ผ์ ํ์์
๋๋ค.")
private String email;
private Date createdAt;
private Date updatedAt;
}
Java
๋ณต์ฌ
Controller + Validation
UserController.java
@Slf4j
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* ํ์๊ฐ์
ํ๋ฉด
* @param model
* @param user
* @return
*/
@GetMapping("/signup")
public String signup(Model model, Users user) {
return "signup";
}
/**
* ํ์๊ฐ์
์ฒ๋ฆฌ
* @param user
* @param bindingResult
* @return
* @throws Exception
* โญ @Validated : ์ง์ ํ ๊ฐ์ฒด์ ๋ํด์ ์ ํจ์ฑ ๊ฒ์ฌ ์คํ
* โญ BindingResult : ์ ํจ์ฑ ๊ฒ์ฌ์์ ๋ฐ์ํ ์๋ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ ๊ฐ์ฒด
*/
@PostMapping("/signup")
public String signup(
@Validated @ModelAttribute("user") Users user,
BindingResult bindingResult
) throws Exception {
// ์ ํจ์ฑ ๊ฒ์ฆ ์๋ฌ ๋ฐ์ํ๋ฉด โก ๋ค์ ํ์๊ฐ์
ํ๋ฉด์ผ๋ก
if( bindingResult.hasErrors() ) {
return "signup";
}
// ์ ํจ์ฑ ๊ฒ์ฆ ์ฑ๊ณตํ๋ฉด, ํ์๊ฐ์
์ฒ๋ฆฌ ํ ๋ฉ์ธํ๋ฉด์ผ๋ก
boolean result = userService.insert(user);
log.info("ํ์๊ฐ์
์ฑ๊ณต ์ฌ๋ถ : " + result);
return "redirect:/";
}
}
Java
๋ณต์ฌ
BindingResult ๋์ ๊ตฌ์กฐ
Spring์ Validation ์ฒ๋ฆฌ ๊ณผ์ ์์ BindingResult๋ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ๋์ํฉ๋๋ค:
1.
์ ํจ์ฑ ๊ฒ์ฌ ์คํ: @Validated ์ด๋
ธํ
์ด์
์ด ๋ถ์ ๊ฐ์ฒด์ ๋ํด ๊ฒ์ฆ ์คํ
2.
์ค๋ฅ ์์ง: BindingResult ๊ฐ์ฒด๊ฐ ๊ฒ์ฆ ๊ณผ์ ์์ ๋ฐ์ํ ๋ชจ๋ ์ค๋ฅ ์ ๋ณด๋ฅผ ์์ง
โข
ํ๋ ์ค๋ฅ (Field Errors)
โข
๊ธ๋ก๋ฒ ์ค๋ฅ (Global Errors)
3.
์ค๋ฅ ์ฒ๋ฆฌ: bindingResult.hasErrors()๋ก ์ค๋ฅ ์กด์ฌ ์ฌ๋ถ ํ์ธ
โข
true ๋ฐํ ์ ํผ ํ์ด์ง๋ก ๋ค์ ์ด๋
โข
์ค๋ฅ ๋ฉ์์ง๋ ์๋์ผ๋ก Model์ ํฌํจ๋์ด ๋ทฐ๋ก ์ ๋ฌ
์์ ์ฝ๋ ๋์:
// 1. ํผ์์ ๋ฐ์ดํฐ ์ ์ถ
// 2. @Validated๋ก ๊ฒ์ฆ ์คํ
// 3. BindingResult์ ์ค๋ฅ ์ ๋ณด ์ ์ฅ
if (bindingResult.hasErrors()) {
// 4. ์ค๋ฅ ๋ฐ์ ์ ํผ ํ์ด์ง๋ก ๋ฆฌํด
// - ์ค๋ฅ ๋ฉ์์ง๊ฐ ์๋์ผ๋ก ๋ทฐ๋ก ์ ๋ฌ๋จ
// - form:errors ํ๊ทธ๋ฅผ ํตํด ์ค๋ฅ ํ์
return "join";
}
// 5. ๊ฒ์ฆ ํต๊ณผ ์ ์ ์ ์ฒ๋ฆฌ
Java
๋ณต์ฌ
View์์์ ์ค๋ฅ ํ์:
<form:errors path="username" cssClass="error" />
<!-- username ํ๋์ ์ค๋ฅ๊ฐ ์์ ๊ฒฝ์ฐ ๋ฉ์์ง ํ์ -->
HTML
๋ณต์ฌ
BindingResult์ ์๋ฌ ๋ฉ์์ง ์ฒ๋ฆฌ ๊ณผ์
BindingResult๊ฐ ํผ์ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
1.
์๋ฌ ๊ฐ์ง: Validation ์ด๋
ธํ
์ด์
๊ฒ์ฆ ์คํจ ์ ์๋ฌ ์ ๋ณด๊ฐ ์์ฑ๋จ
2.
์๋ฌ ์ ์ฅ: BindingResult ๊ฐ์ฒด์ ์๋ฌ ์ ๋ณด ์๋ ์ ์ฅ
โข
ํ๋ ์๋ฌ: FieldError ๊ฐ์ฒด๋ก ์ ์ฅ
โข
๊ธ๋ก๋ฒ ์๋ฌ: ObjectError ๊ฐ์ฒด๋ก ์ ์ฅ
3.
Model ์ ๋ฌ: Spring MVC๊ฐ ์๋์ผ๋ก BindingResult๋ฅผ Model์ ์ถ๊ฐ
โข
๋ณ๋์ model.addAttribute() ๋ถํ์
โข
๋ทฐ์์ ์๋์ผ๋ก ์ ๊ทผ ๊ฐ๋ฅ
4.
View ์ถ๋ ฅ: form:errors ํ๊ทธ๊ฐ ํด๋น ์๋ฌ ๋ฉ์์ง ํ์
โข
ํ๋๋ณ ์๋ฌ: <form:errors path="ํ๋๋ช
" />
โข
๊ธ๋ก๋ฒ ์๋ฌ: <form:errors path="*" />
// ์์: ์ปจํธ๋กค๋ฌ์์์ ์ฒ๋ฆฌ
@PostMapping("/join")
public String join(@Validated User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// ์๋์ผ๋ก ์๋ฌ ์ ๋ณด๊ฐ ๋ทฐ๋ก ์ ๋ฌ๋จ
return "join";
}
// ๊ฒ์ฆ ํต๊ณผ์ ์ฒ๋ฆฌ
}
Java
๋ณต์ฌ
View ์์๋ ๋ค์๊ณผ ๊ฐ์ด ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ํ ์ ์์ต๋๋ค:
<form:form modelAttribute="user">
<form:input path="username" />
<form:errors path="username" cssClass="error" />
<!-- ๋ชจ๋ ๊ธ๋ก๋ฒ ์๋ฌ ํ์ -->
<form:errors path="*" cssClass="error" />
</form:form>
HTML
๋ณต์ฌ
form:errors์ FieldError์ ๊ด๊ณ
form:errors ํ๊ทธ๋ BindingResult์ ์ ์ฅ๋ FieldError ๊ฐ์ฒด๋ก๋ถํฐ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๊ฐ์ ธ์ ํ์ํฉ๋๋ค. ์ด ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
1.
FieldError ์์ฑ: ๊ฒ์ฆ ์คํจ ์ Spring์ด ์๋์ผ๋ก FieldError ๊ฐ์ฒด ์์ฑ
โข
ํ๋๋ช
โข
๊ฑฐ์ ๋ ๊ฐ
โข
์๋ฌ ๋ฉ์์ง
2.
๋ฉ์์ง ๊ฒ์: form:errors ํ๊ทธ๊ฐ BindingResult์์ ํด๋น ํ๋์ FieldError๋ฅผ ์ฐพ์
3.
๋ฉ์์ง ์ถ๋ ฅ: FieldError์์ ์ค์ ๋ ๋ฉ์์ง๋ฅผ ํ๋ฉด์ ํ์
์์ ์ฝ๋:
// ์ปจํธ๋กค๋ฌ์์ FieldError ํ์ธ
if (bindingResult.hasFieldErrors("username")) {
FieldError error = bindingResult.getFieldError("username");
String message = error.getDefaultMessage(); // @NotBlank ๋ฑ์์ ์ค์ ํ ๋ฉ์์ง
}
Java
๋ณต์ฌ
View์์๋ ๊ฐ๋จํ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ:
<form:errors path="username" /> <!-- username ํ๋์ ๋ชจ๋ ์๋ฌ ๋ฉ์์ง ํ์ -->
HTML
๋ณต์ฌ
MyBatis Mapper ์ค์
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace="๋งคํผ ์ธํฐํ์ด์ค ๊ฒฝ๋ก" -->
<mapper namespace="com.aloha.validation.mapper.UserMapper">
<insert id="insert">
INSERT INTO user( id, username, password, name, email )
VALUES ( #{id}, #{username}, #{password}, #{name}, #{email} )
</insert>
</mapper>
XML
๋ณต์ฌ
UserMapper.java
@Mapper
public interface UserMapper {
// ํ์ ๋ฑ๋ก
int insert(Users user) throws Exception;
}
Java
๋ณต์ฌ
Service
โข
UserService.java
โข
UserServiceImpl.java
UserService.java
public interface UserService {
boolean insert(Users user) throws Exception;
}
Java
๋ณต์ฌ
UserServiceImpl.java
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public boolean insert(Users user) throws Exception {
int result = userMapper.insert(user);
return result > 0;
}
}
Java
๋ณต์ฌ
ํผ ๊ตฌ์ฑ (spring form ํ๊ทธ)
signup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ํ์๊ฐ์
</title>
</head>
<body>
<h2>ํ์๊ฐ์
</h2>
<form th:action="@{/signup}" th:object="${user}" method="post">
<div>
์์ด๋ : <input type="text" th:field="*{username}">
<span th:errors="*{username}" class="error"></span>
</div>
<div>
๋น๋ฐ๋ฒํธ : <input type="password" th:field="*{password}">
<span th:errors="*{password}" class="error"></span>
</div>
<div>
์ด๋ฆ : <input type="text" th:field="*{name}">
<span th:errors="*{name}" class="error"></span>
</div>
<div>
์ด๋ฉ์ผ : <input type="email" th:field="*{email}">
<span th:errors="*{email}" class="error"></span>
</div>
<button type="submit">ํ์๊ฐ์
</button>
</form>
</body>
</html>
HTML
๋ณต์ฌ
Thymeleaf Form ํ๊ทธ ์ค๋ช
ํ๊ทธ/์์ฑ | ์ค๋ช
| ์์ |
th:object | ํผ๊ณผ ๋ฐ์ธ๋ฉํ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ง์ ํฉ๋๋ค.
์ปจํธ๋กค๋ฌ์์ ์ ๋ฌ๋ ๊ฐ์ฒด์ ์ฐ๊ฒฐ๋ฉ๋๋ค. | <form th:object="${user}"> |
th:field | ๋ชจ๋ธ ๊ฐ์ฒด์ ํน์ ํ๋์ input ์์๋ฅผ ๋ฐ์ธ๋ฉํฉ๋๋ค.
id, name, value ์์ฑ์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. | <input th:field="*{username}" /> |
#fields.hasErrors() | ํน์ ํ๋์ ์ ํจ์ฑ ๊ฒ์ฆ ์ค๋ฅ๊ฐ ์๋์ง ํ์ธํฉ๋๋ค.
์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ฌ์ฉ๋ฉ๋๋ค. | th:if="${#fields.hasErrors('username')}" |
th:errors | ํน์ ํ๋์ ์ ํจ์ฑ ๊ฒ์ฆ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ํฉ๋๋ค.
BindingResult์ ์ค๋ฅ ์ ๋ณด๋ฅผ ์ถ๋ ฅํฉ๋๋ค. | <span th:errors="*{username}"></span> |
*{field} | th:object๋ก ์ง์ ๋ ๊ฐ์ฒด์ ํ๋๋ฅผ
์ฐธ์กฐํ๋ ์ ํ ๋ณ์ ํํ์์
๋๋ค. | *{username}์${user.username}๊ณผ ๋์ผ |
th:action | ํผ ์ ์ถ ์ ์์ฒญํ URL์ ์ง์ ํฉ๋๋ค.
@{} ํํ์์ผ๋ก ์ปจํ
์คํธ ๊ฒฝ๋ก๊ฐ ์๋์ผ๋ก ์ถ๊ฐ๋ฉ๋๋ค. | th:action="@{/join}" |
th:field="*{username}"์ ์ฌ์ฉํ๋ฉด ๋ค์ ์์ฑ๋ค์ด ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค:
โข
id="username"
โข
name="username"
โข
value="${user.username}" (๊ธฐ์กด ๊ฐ์ด ์๋ ๊ฒฝ์ฐ)
Spring Form ํ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ ๊ณตํฉ๋๋ค:
โข
๋ชจ๋ธ ๊ฐ์ฒด์ ์๋ ๋ฐ์ธ๋ฉ
โข
์ ํจ์ฑ ๊ฒ์ฌ ์ค๋ฅ ๋ฉ์์ง ํ์ ๊ธฐ๋ฅ
โข
HTML ํผ ์์๋ค์ ๋ ์ฝ๊ฒ ์ฒ๋ฆฌ
โข
Spring์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ๊ธฐ๋ฅ๊ณผ ์๋ฒฝํ ํตํฉ
ํต์ฌ ์ ๋ฆฌ
์์ | ์ค๋ช
|
DTO | ์
๋ ฅ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ ์ค์ |
Controller | @Validated, BindingResult๋ก ์
๋ ฅ ๊ฒ์ฆ ์ฒ๋ฆฌ |
Service | ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ |
MyBatis | DB ์ ์ฅ ์ฒ๋ฆฌ |
View (html) | spring form ํ๊ทธ๋ก ์
๋ ฅ ๋ฐ ์ค๋ฅ ํ์ |




