SpringBoot [Bean Validation]
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์ ํ์ฉํ์ง ์์ผ๋ฉฐ ๊ณต๋ฐฑ ๋ฌธ์์ด์ ํ์ฉํ์ง ์์
์ด์ธ์ ๋ค๋ฅธ ์ด๋ ธํ ์ด์ ๋ค์ ์๋ ์ฌ์ดํธ์ ์ ์ํ๋ฉด ๋๋ค.
โพ ์ด๋ ธํ ์ด์ ์ด๋ป๊ฒ ์ฐ๋์ง ํ์ธํด๋ณด๊ธฐ
@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์ ๋ด๊ธฐ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ๊ผญ ์ด๋ฆ์ ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค.