Bean Validation์ด๋?
์ ํจ์ฑ์ ๊ฒ์ฌํ๋ JAVA์ ์ ํจ์ฑ ๊ฒ์ฌ ํ์ค ๊ธฐ์ ์ด๋ค. Bean Validation์ ํน์ ํ ๊ตฌํ์ฒด๊ฐ ์๋๋ผ Bean Validation 2.0์ด๋ผ๋ ๊ธฐ์ ํ์ค์ด๋ค. ๊ฒ์ฆ ์ ๋ ธํ ์ด์ ๊ณผ ์ฌ๋ฌ ์ธํฐํ์ด์ค์ ๋ชจ์์ด๋ค.
โญ ์ผ๋จ bean validation์ ์ฌ์ฉํ๊ธฐ ์ ์ ์์กด๊ด๊ณ๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค!
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
๐ช ์ด๋ ธํ ์ด์ ์ ๋ฆฌ
- @NotBlank : ๋น ๊ฐ + ๊ณต๋ฐฑ๋ง ์๋ ๊ฒฝ์ฐ๋ฅผ ํ์ฉํ์ง ์์
- @NotNull : null์ ํ์ฉํ์ง ์์
- @Range(min = 10, max = 100) : 10๋ถํฐ 100 ๋ฒ์ ์์ ๊ฐ๋ง ํ์ฉํจ
- @Max(100) : ์ต๋ 100๊น์ง๋ง ํ์ฉํจ
- @Email : emailํ์์ ๊ฐ์ถ์ด์ผ ํจ
- @NotEmpty : null์ ํ์ฉํ์ง ์์ผ๋ฉฐ ๊ณต๋ฐฑ ๋ฌธ์์ด์ ํ์ฉํ์ง ์์
์ด์ธ์ ๋ค๋ฅธ ์ด๋ ธํ ์ด์ ๋ค์ ์๋ ์ฌ์ดํธ์ ์ ์ํ๋ฉด ๋๋ค.
Hibernate Validator 6.2.3.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th
docs.jboss.org
โพ ์ด๋ ธํ ์ด์ ์ด๋ป๊ฒ ์ฐ๋์ง ํ์ธํด๋ณด๊ธฐ
@Data
public class Item {
@NotBlank
private String itemName;
@NotNull
@Range(min = 10, max = 1000)
private Integer price;
@NotNull
@Max(value = 1000)
private Integer quantity;
}
์์ดํ ์ด๋ฆ์ ๋น์นธ ๋๋ ๊ณต๋ฐฑ์ด์ด์๋ ์ ๋๋ค.
๊ฐ๊ฒฉ์ 10 ์ด์ 1000 ์ดํ์ ๊ฐ์ด์ด์ผ ํ๋ค.
์๋์ 1000์ ๋์ด๊ฐ๋ฉด ์ ๋๋ค.
๐ช ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํด๋ณด์๋ฉด
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "items/addForm";
}
}
๐ ์ ํจ์ฑ์ ๊ฒ์ฌํ๊ธฐ ์ํด์๋ @Valid ๋๋ @Validated ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
@Validated์ผ๋ก ๊ฒ์ฆํ ๊ฐ์ฒด๊ฐ ์ ํจํ์ง ์์ ๊ฐ์ฒด๋ผ๋ฉด Controller์ ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์๋ BindingResult ์ธํฐํ์ด์ค๋ฅผ ํ์ฅํ ๊ฐ์ฒด๋ก ๋ค์ด์จ๋ค. ๋ฐ๋ผ์ bindingResult.hasError() ๋ฉ์๋๋ ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ ๊ฒฝ์ฐ true๋ฅผ ๋ฐํํด์ ๊ฒ์ฆํ๋ ๊ฒ์ด๋ค. ๋ฐ์ธ๋ฉ์ ์ฑ๊ณตํ ํ๋๋ง Bean Validation์ ์ ์ฉํ๋ค. typeMismatch ๊ฐ์ ๊ฒฝ์ฐ๋ FieldError์ ์ถ๊ฐํด์ BindingResult์ ๋ด๋ ๊ฒ ๊ฐ๋ค!
โญ @Valid vs @Validated์ ์ฐจ์ด์
@Valid์ @Validated๋ชจ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ ๋ ์ฌ์ฉ ๊ฐ๋ฅํ ์ด๋ ธํ ์ด์ ์ด๋ค. ํ์ง๋ง @Valid๋ Java์์ ์ง์ํ๋ ์ด๋ ธํ ์ด์ ์ด๊ณ @Validated์ Spring์์ ์ง์ํ๋ ์ด๋ ธํ ์ด์ ์ด๋ค. ๋์ค์ groups๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ์๋ @Valid๋ฅผ ์ฌ์ฉํ๋ฉด ์ ๋๋ค! ์ด๊ฑด Spring์์ ์ง์ํ๋ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์! ์ฌ๋งํด์๋ ์ ์ฉํ ๊ธฐ๋ฅ์ด ๋ง์ @Validated์ ์ฐ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.
๐ช ์๋ฌ ์ฝ๋ ์์
bean validation์ ์ด์ฉํ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ๋ํดํธ ์๋ฌ์ฝ๋๊ฐ ์กด์ฌํ๋ค! ํ์ง๋ง ๋ ๊ฐ๊ฒฐํ๊ฑฐ๋ ๋ณด๊ธฐ ์ข๊ฒ ๋ฐ๊พธ๊ณ ์ถ์ผ๋ฉด ์๋ฌ์ฝ๋๋ฅผ ์์ ํ๋ฉด ๋๋ค.
errors.properties์ ์ด๋ฐ ์์ผ๋ก ์ถ๊ฐ์์ผ์ฃผ๋ฉด ๋๋ค.
NotBlank={0} ๊ณต๋ฐฑX
Range={0}, {2} ~ {1} ํ์ฉ
Max={0}, ์ต๋ {1}
{0}์ ํ๋๋ช ์ ์๋ฏธํ๋ค. itemName, price, quantity ๊ฐ์ ๊ฑฐ!
๊ทผ๋ฐ ์์ ๋ฐฉ๋ฒ์ ๊ฐ์ฅ ์ฐ์ ์์๊ฐ ๋ฎ๋ค!
์ ๊ฑฐ๋ณด๋ค ์ฐ์ ์์๋ฅผ ๋๊ฒ ํ๋ ค๋ฉด ์ ์ด์
@NotBlank(message = "๊ณต๋ฐฑ์ ์
๋ ฅํ ์ ์์ต๋๋ค.")
bean validation ์ด๋ ธํ ์ด์ ์ ๋ ฅํ ๋ message ์์ฑ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
์๋๋ฉด errors.properties์ ๋ ์์ธํ ๊ฒฝ๋ก๋ก ์ง์ ํด์ฃผ๋ฉด ๋๋ค.
typeMismatch.java.lang.Integer="์ซ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์"
๐ช Bean Validation ํ๊ณ
๋ง์ฝ ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ ๋์ ์์ ํ ๋์ ์๊ตฌ์ฌํญ์ด ๋ค๋ฅผ ๊ฒฝ์ฐ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
ex) ํ์๊ฐ์ ์ ํ ๋์๋ ๋์ด๊ฐ ํ์ ์ ๋ ฅ ๊ฐ์ด๋ ์์ ํ ๋๋ ๋์ด๋ฅผ ์ ์ง ์์๋ ๊ด์ฐฎ์ ๊ฒฝ์ฐ!
์ด๋ด ๊ฒฝ์ฐ์๋ ์๋ก ์ถฉ๋ํ๊ฒ ๋๋ฉด์ ๋ฌธ์ ๊ฐ ์๊ธด๋ค!!
์ด๋ฅผ ํด๊ฒฐํ ๋ฐฉ์์ ๋ ๊ฐ์ง์ด๋ค
โซ groups
groups๋ฅผ ์ฌ์ฉํ๋ฉด ์ญํ ์ ๋ถ๋ฆฌ์ํฌ ์ ์๋ค. ๋ฑ๋ก๊ณผ ์์ ์ ๊ฐ๊ฐ ๋ค๋ฅธ ๊ทธ๋ฃน์ผ๋ก ๋๋๋ ๊ฒ์ด๋ค.
์์์ ๋งํ๋ฏ์ด ์ด๊ฑด ์คํ๋ง์ด ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์ @Validated๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค!
๋ฑ๋ก์ฉ ์ธํฐํ์ด์ค
public interface SaveCheck {}
์์ ์ฉ ์ธํฐํ์ด์ค
public interface UpdateCheck {}
์ด๋ ๊ฒ ๊ฐ๊ฐ ๋ง๋ค์ด์ฃผ๊ณ bean validation ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ๊ทธ๋ฃน์ ์ง์ ํด์ฃผ๋ฉด ๋๋ค.
@Data
public class Members {
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
private String name;
@NotNull(groups = SaveCheck.class)
private Integer age;
}
์ด ์๋ฏธ๋ name์๋ ๋ ๊ทธ๋ฃน ๋ชจ๋ @NotNull ์ด๋ ธํ ์ด์ ์ ์ ์ฉ์ํค๊ณ , age๋ ํ์์ ๋ฑ๋กํ ๋๋ง ์ ์ฉ์ํจ๋ค๋ ์๋ฏธ
๊ทธ๋ผ ์ปจํธ๋กค๋ฌ์๋ ๊ทธ๋ฃน์ ์ง์ ํด์ฃผ์ด์ผ ํ๋ค.
public String addMember(@Validated(SaveCheck.class)) {}
โญ ๊ทผ๋ฐ ์ด ๋ฐฉ๋ฒ์ ๋๋ฌด ์ฝ๋๊ฐ ๋ณต์กํด ๋ณด์ธ๋ค. ๊ทธ๋์ ์ค๋ฌด์์ ์ ์ ์ด๋ค๊ณ ํ๋ค!
์ปจํธ๋กค๋ฌ๋ฅผ ๋ถ๋ฆฌํด์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ด ๋ ๊ฐ๋จํ๊ณ ์ข๋ค
๋ค์ Members ์ปจํธ๋กค๋ฌ๋ฅผ ์๋๋๋ก ๋ฐ๊พธ์ด์ฃผ๊ณ ,
@Data
public class Members {
private String name;
private Integer age;
}
@Data
public class MemberSaveForm {
@NotNull
private String name;
@NotNull
private Integer age;
}
@Data
public class MemberUpdateForm {
@NotNull
private String name;
private Integer age;
}
์ด๋ ๊ฒ ํผ์ ๋ ๊ฐ๋ก ๋๋์ด์ฃผ๊ณ ๋์
public String addItem(@Validated @ModelAttribute("Members") memberSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "Members/addForm";
}
//์ฑ๊ณต ๋ก์ง
Members members = new Members();
members.setMemberName(form.getMemberName());
members.setAge(form.getAge());
Members savedMember = membersRepository.save(members);
redirectAttributes.addAttribute("itemId", savedMember.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/items/{itemId}";
}
์ด๋ฐ ์์ผ๋ก ์ปจํธ๋กค๋ฌ๋ฅผ ์์ ํ๋ฉด ๋๋ค.
@ModelAttribute๋ฅผ ์ ์์ ํ๋, ํผ์ ๋ ๊ฐ๋ก ๋๋๊ณ ๋์ ์ ์ฉํด์ผ ํ๋ bean validation ์ด๋ ธํ ์ด์ ์ด ๋ค๋ฅธ๋ฐ, model attribute์ ("Members")๋ฅผ ์ ํด์ฃผ๊ฒ ๋๋ฉด
model.addAttribute("memberSaveForm", form);
์ด๋ ๊ฒ memberSaveForm์ด๋ผ๋ MVC Model์ ๋ด๊ธฐ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ๊ผญ ์ด๋ฆ์ ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค.