03.Validation
Validation
Spring Verification
컨νΈλ‘€λ¬μ μ€μν μν μ€ νλλ HTTP μμ²μ΄ μ μμΈμ§ κ²μ¦νλ κ²
ν΄λΌμ΄μΈνΈ κ²μ¦μ μ‘°μν μ μμΌλ―λ‘ λ³΄μμ μ·¨μ½
κ·Έλ λ€κ³ μλ²λ§μΌλ‘ κ²μ¦νλ©΄ μ¦κ°μ μΈ κ³ κ° μ¬μ©μ±μ΄ λΆμ‘±
μλ², ν΄λΌμ΄μΈνΈ κ²μ¦μ μ μ ν μμ΄μ μ¬μ©νλ μλ² κ²μ¦μ νμ
API λ°©μ μ¬μ© μ, API μ€νμ μ μ μν΄μ κ²μ¦ μ€λ₯λ₯Ό API μλ΅ κ²°κ³Όμ μ λ¨κ²¨μΌ ν¨
BindingResult
μ€νλ§μ΄ μ 곡νλ κ²μ¦ μ€λ₯λ₯Ό 보κ΄νλ κ°μ²΄
@PostMapping("/add")
public String addItem(@ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model) {
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, null, null, "μν μ΄λ¦μ νμ μ
λλ€."));
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, null, null, "κ°κ²©μ 1,000 ~ 1,000,000 κΉμ§ νμ©ν©λλ€."));
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, null ,null, "μλμ μ΅λ 9,999 κΉμ§ νμ©ν©λλ€."));
}
// κΈλ‘λ² μμΈ
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item",null ,null, "κ°κ²© * μλμ ν©μ 10,000μ μ΄μμ΄μ΄μΌ ν©λλ€. νμ¬ κ° = " + resultPrice));
}
}
// κ²μ¦ μ€ν¨ μ μ
λ ₯ νΌμΌλ‘
if (bindingResult.hasErrors()) {
log.info("errors={} ", bindingResult);
return "validation/v2/addForm";
}
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v2/items/{itemId}";
}
...
/**
* FieldError class
* νλμ μ€λ₯κ° μμΌλ©΄ FieldError κ°μ²΄λ₯Ό μμ±ν΄μ bindingResult μ λ΄μλμ.
*/
public FieldError(
String objectName, // @ModelAttribute μ΄λ¦
String field, // μ€λ₯κ° λ°μν νλ μ΄λ¦
String defaultMessage // μ€λ₯ κΈ°λ³Έ λ©μμ§
) {}
public FieldError(
String objectName, // μ€λ₯κ° λ°μν κ°μ²΄ μ΄λ¦
String field, // μ€λ₯ νλ
@Nullable Object rejectedValue, // μ¬μ©μκ° μ
λ ₯ν κ°(κ±°μ λ κ°)
boolean bindingFailure, // νμ
μ€λ₯ κ°μ λ°μΈλ© μ€ν¨μΈμ§, κ²μ¦ μ€ν¨μΈμ§ κ΅¬λΆ κ°
@Nullable String[] codes, // λ©μμ§ μ½λ
@Nullable Object[] arguments, // λ©μμ§μμ μ¬μ©νλ μΈμ
@Nullable String defaultMessage // κΈ°λ³Έ μ€λ₯ λ©μμ§
)
/**
* ObjectError
* νΉμ νλλ₯Ό λμ΄μλ μ€λ₯κ° μμΌλ©΄ ObjectError κ°μ²΄λ₯Ό μμ±ν΄μ bindingResult μ λ΄μλμ.
*/
public ObjectError(
String objectName, // @ModelAttribute μ μ΄λ¦
String defaultMessage // μ€λ₯ κΈ°λ³Έ λ©μμ§
) {}BindingResult
@ModelAttribute κ°μ²΄ λ€μ μμΉ
BindingResult κ° μμΌλ©΄ @ModelAttribute μ λ°μ΄ν° λ°μΈλ© μ μ€λ₯κ° λ°μν΄λ 컨νΈλ‘€λ¬ νΈμΆ
BindingResult κ° μμΌλ©΄ 400 μ€λ₯ λ°μ ν, μ€λ₯ νμ΄μ§λ‘ μ΄λ
BindingResult κ° μμΌλ©΄ μ€λ₯ μ 보(FieldError)λ₯Ό BindingResult μ λ΄μμ 컨νΈλ‘€λ¬ μ μ νΈμΆ
Model μ μλμΌλ‘ ν¬ν¨
μ΄λ€ κ°μ²΄λ₯Ό λμμΌλ‘ κ²μ¦νλμ§ νκ²μ μκ³ μμ
.
BindingResult μ κ²μ¦ μ€λ₯λ₯Ό μ μ©νλ μΈ κ°μ§ λ°©λ²
@ModelAttribute κ°μ²΄μ νμ μ€λ₯ λ±, λ°μΈλ© μ€ν¨ μ μ€νλ§μ΄ FieldError μμ± ν BindingResult μ μ½μ
κ°λ°μκ° μ§μ μ λ ₯
Validator μ¬μ©
Error Message
μ€λ₯ λ©μμ§ νμΌμ μΈμν μ μλλ‘ μ€μ μΆκ°
errors.properties
rejectValue() , reject()
BindingResult λ κ²μ¦ν΄μΌ target κ°μ²΄λ₯Ό μκ³ μμ
BindingResult μ
rejectValue(),reject()λ₯Ό μ¬μ©νλ©΄ FieldError, ObjectError λ₯Ό μ§μ μμ±νμ§ μκ³ , κΉλνκ² κ²μ¦ μ€λ₯λ₯Ό λ€λ£° μ μμ
Apply Thymeleaf
νλ μ€λ₯ μ²λ¦¬
rejectedValue(): μ€λ₯ λ°μ μ μ¬μ©μ μ λ ₯ κ°μ μ μ₯
th:fieldλ νμμλ λͺ¨λΈ κ°μ²΄μ κ°μ μ¬μ©νμ§λ§, μ€λ₯κ° λ°μνλ©΄ FieldError μμ 보κ΄ν κ°μ μ¬μ©ν΄μ κ°μ μΆλ ₯
κΈλ‘λ² μ€λ₯ μ²λ¦¬
MessageCodesResolver
κ²μ¦ μ€λ₯ μ½λλ‘ λ©μμ§ μ½λλ€μ μμ±
MessageCodesResolverλ μΈν°νμ΄μ€,DefaultMessageCodesResolverλ κΈ°λ³Έ ꡬν체ObjectError,FieldErrorμ μ£Όλ‘ ν¨κ» μ¬μ©
DefaultMessageCodesResolver κΈ°λ³Έ λ©μμ§ μμ± κ·μΉ
νλ μ€λ₯
κ°μ²΄ μ€λ₯
λμ λ°©μ
rejectValue(),reject()λ λ΄λΆμμMessageCodesResolverμ¬μ© β μ¬κΈ°μ λ©μμ§ μ½λ μμ±FieldError, ObjectError μμ±μλ₯Ό 보면 μ μ μλ―μ΄, μ¬λ¬ μ€λ₯ μ½λλ₯Ό κ°μ§ μ μμ
MessageCodesResolverλ₯Ό ν΅ν΄ μμ±λ μμλλ‘ μ€λ₯ μ½λ 보κ΄
μ€λ₯ μ½λ κ΄λ¦¬ μ λ΅
ꡬ체μ μΈ κ² β λ ꡬ체μ μΈ κ²μΌλ‘κ° ν΅μ¬
MessageCodesResolver λ ꡬ체μ μΈ κ²(required.item.itemName)μ λ¨Όμ μμ±νκ³ , λ ꡬ체μ μΈ κ²(required)μ λμ€μ μμ±
μ€μνμ§ μμ λ©μμ§λ λ²μ©μ± μλ κ°λ¨ν λ©μμ§(requried)λ‘, μ€μν λ©μμ§λ νμν λ ꡬ체μ μΌλ‘ μ¬μ©νλ λ°©μμ΄ ν¨κ³Όμ
errors.properties
ValidationUtils
ValidationUtils μ¬μ© μ
ValidationUtils μ¬μ© ν
Spring κ²μ¦ μ€λ₯ λ©μμ§
Spring μ νμ μ€λ₯κ° λ°μνλ©΄ typeMismatch μ€λ₯ μ½λλ₯Ό μ¬μ©
μ£Όλ‘ νμ μ λ³΄κ° λ§μ§ μμ κ²½μ° Spring μ§μ κ²μ¦
typeMismatchμ€λ₯ μ½λκ°MessageCodesResolverλ₯Ό ν΅ν΄ 4κ°μ§ λ©μμ§ μ½λλ‘ μμ±typeMismatch.item.price
typeMismatch.price
typeMismatch.java.lang.Integer
typeMismatch
errors.properties
Validator
μ€νλ§μ 체κ³μ μΌλ‘ κ²μ¦ κΈ°λ₯μ μ 곡νκΈ° μν΄ Validator μΈν°νμ΄μ€ μ 곡
κ²μ¦λ‘μ§ λΆλ¦¬μ νΈμΆ
WebDataBinder
WebDataBinder λ μ€νλ§μ νλΌλ―Έν° λ°μΈλ© μν μ ν΄μ£Όκ³ , κ²μ¦ κΈ°λ₯λ λ΄λΆμ ν¬ν¨
WebDataBinderμ κ²μ¦κΈ° μΆκ° μ ν΄λΉ 컨νΈλ‘€λ¬μμλ κ²μ¦κΈ°λ₯Ό μλ μ μ©WebDataBinderλ₯Ό ν΅ν΄ItemValidatorνΈμΆ
@Validated
κ²μ¦κΈ°λ₯Ό μ€ννλΌλ μ λ Έν μ΄μ
WebDataBinderμ λ±λ‘ν κ²μ¦κΈ°λ₯Ό μ°Ύμμ μ€νμ¬λ¬ κ²μ¦κΈ°κ° λ±λ‘λλ€λ©΄ Validator.supports() λ₯Ό ν΅ν΄ ꡬλΆ
μ¬κΈ°μλ Validator.supports(Item.class) νΈμΆ ν, κ²°κ³Όκ° true μ΄λ―λ‘ ItemValidator μ validate() νΈμΆ
bindingResult μ κ²μ¦ κ²°κ³Όκ° λ΄κΉ
Bean Validation
κ²μ¦ λ‘μ§μ λͺ¨λ νλ‘μ νΈμ μ μ©ν μ μκ² κ³΅ν΅ννκ³ , νμ€ν
μ λ Έν μ΄μ νλλ‘ κ²μ¦ λ‘μ§μ λ§€μ° νΈλ¦¬νκ² μ μ©
νΉμ ν ꡬνμ²΄κ° μλλΌ Bean Validation 2.0(JSR-380)μ΄λΌλ κΈ°μ νμ€
κ²μ¦ μ λ Έν μ΄μ κ³Ό μ¬λ¬ μΈν°νμ΄μ€μ λͺ¨μ
λ§μΉ JPA κ° νμ€ κΈ°μ μ΄κ³ κ·Έ ꡬνμ²΄λ‘ νμ΄λ²λ€μ΄νΈκ° μλ κ²κ³Ό μ μ¬
μΌλ°μ μΌλ‘ μ¬μ©νλ ꡬν체λ Hibernate Validator
ORM κ³Όλ 무κ΄..
Hibernate Validator Reference
dependence
Jakarta Bean Validation
jakarta.validation-api: Bean Validation μΈν°νμ΄μ€hibernate-validator: ꡬν체
κ²μ¦ μ λ Έν μ΄μ μ μ©
Apply Bean Validation in Spring
μ€νλ§ λΆνΈλ spring-boot-starter-validation λΌμ΄λΈλ¬λ¦¬κ° μΆκ°λλ©΄ μλμΌλ‘ Bean Validator λ₯Ό μΈμ§νκ³ μ€νλ§μ ν΅ν©
Spring Boot λ μλμΌλ‘
LocalValidatorFactoryBeanμGlobal Validatorλ‘ λ±λ‘LocalValidatorFactoryBeanμ Annotation κΈ°λ° κ²μ¦
@Valid(μλ° νμ€),@Validated(μ€νλ§ μ μ©) λ§ μ μ©νμ¬ κ²μ¦ μ¬μ© κ°λ₯κ²μ¦ μ€λ₯ λ°μ μ FieldError, ObjectError λ₯Ό μμ±ν΄μ BindingResult μ λ΄μ μ€λ€.
ItemValidator κ° λ±λ‘λμ΄ μλ€λ©΄ μ€λ₯ κ²μ¦κΈ° μ€λ³΅μ λ§κΈ° μν΄ μ κ±°κ° νμ
κ²μ¦ μμ
\1. @ModelAttribute κ° νλμ νμ λ³ν μλ
λ³νμ μ±κ³΅ν νλλ§ BeanValidation μ μ©
μ€ν¨νλ©΄ typeMismatch λ‘ FieldError μΆκ°
\2. BeanValidation
λ°μΈλ©μ μ±κ³΅ν νλλ§ BeanValidation μ μ©
λ°μΈλ©μ μ€ν¨ν νλλ μ μ©νμ§ μμ
μλ¬ μ½λ
μ λ Έν μ΄μ μ€λ₯ μ½λλ₯Ό κΈ°λ°μΌλ‘ MessageCodesResolver λ₯Ό ν΅ν΄ λ€μν λ©μμ§ μ½λκ° μμλλ‘ μμ±
@NotBlank
NotBlank.item.itemName
NotBlank.itemName
NotBlank.java.lang.String
NotBlank
@Range
Range.item.price
Range.price
Range.java.lang.Integer
Range
BeanValidation λ©μμ§ μ°Ύλ μμ
μμ±λ λ©μμ§ μ½λ μμλλ‘ messageSource μμ λ©μμ§ μ°ΎκΈ°
μ λ Έν μ΄μ μ message μμ± μ¬μ© β @NotBlank(message = "곡백μ μ λ ₯ν μ μμ΅λλ€.")
λΌμ΄λΈλ¬λ¦¬κ° μ 곡νλ κΈ°λ³Έ κ° μ¬μ© β "κ³΅λ°±μΌ μ μμ΅λλ€"
κΈλ‘λ² μ€λ₯
@ScriptAssert() μ¬μ©μ μ μ½μ΄ λ§κ³ κ²μ¦ κΈ°λ₯μ΄ ν΄λΉ κ°μ²΄ λ²μλ₯Ό λμ΄μ€ κ²½μ° λμμ΄ μ΄λ ΅λ€.
κΈλ‘λ² μ€λ₯ κ΄λ ¨ λΆλΆλ§ μλ° μ½λλ‘ μμ±νλ κ²μ κΆμ₯
groups
λ±λ‘μμ κ²μ¦ν κΈ°λ₯κ³Ό μμ μμ κ²μ¦ν κΈ°λ₯μ κ°κ° κ·Έλ£ΉμΌλ‘ λλμ΄ μ μ©
groups κΈ°λ₯μ μ€μ μ μ¬μ©λμ§ μμ
λμ μ€λ¬΄μμλ μ£Όλ‘ λ±λ‘μ© νΌ κ°μ²΄(ItemSaveForm)μ μμ μ© νΌ κ°μ²΄(ItemUpdateForm)λ₯Ό λΆλ¦¬ν΄μ μ¬μ©
μ°Έκ³ . @Valid μλ groups μ μ© κΈ°λ₯μ μ 곡νμ§ μμΌλ―λ‘, groups μ¬μ© μ @Validated λ₯Ό μ¬μ©νμ.
groups μμ±
validation interface λͺ μ
Form μ μ‘ κ°μ²΄ λΆλ¦¬ π
groups κΈ°λ₯μ μ¬μ©νλ©΄ μ λ°μ μΈ λ³΅μ‘λκ° μμΉν΄μ μ€λ¬΄μμλ μ£Όλ‘ νΌ κ°μ²΄λ₯Ό λΆλ¦¬ν΄μ μ¬μ©
λ±λ‘κ³Ό μμ μ λ€λ£¨λ λ°μ΄ν° λ²μμ μ°¨μ΄κ° μλ€λ³΄λ μμ ν λ€λ₯Έ λ°μ΄ν°κ° λμ΄μ¨λ€.
λ°λΌμ, Save/Update λ³λμ κ°μ²΄λ‘ λ°μ΄ν°λ₯Ό μ λ¬λ°λ κ²μ΄ μ’λ€.
νΌ λ°μ΄ν° μ λ¬μ μν λ³λμ κ°μ²΄λ₯Ό μ¬μ©νλ©΄ λ±λ‘, μμ μ΄ μμ ν λΆλ¦¬λκΈ° λλ¬Έμ groups μ μ©μ΄ λΆνμ
Form μ μ‘ κ°μ²΄ λΆλ¦¬
λΆλ¦¬λ μ μ‘ κ°μ²΄ μ μ©
MVC Model μ item μΌλ‘ λ΄κΈ°λλ‘ νκΈ° μν΄ @ModelAttribute("item") μ μ©
HTTP Message Converter
@Valid, @Validated λ HttpMessageConverter(@RequestBody)μλ μ μ© κ°λ₯
@ModelAttributeλ HTTP μμ² νλΌλ―Έν°(URL 쿼리 μ€νΈλ§, POST Form)λ₯Ό λ€λ£° λ μ¬μ©
@RequestBodyλ HTTP Bodyμ λ°μ΄ν°λ₯Ό κ°μ²΄λ‘ λ³νν λ μ¬μ©(μ£Όλ‘ API JSON μμ²)
API μμ² μΌμ΄μ€
μ±κ³΅ μμ²: μ±κ³΅
μ€ν¨ μμ²: JSON μ κ°μ²΄λ‘ μμ±νλ κ² μμ²΄κ° μ€ν¨
κ²μ¦ μ€λ₯ μμ²: JSONμ κ°μ²΄λ‘ μμ±νλ κ²μ μ±κ³΅νμ§λ§, κ²μ¦μμ μ€ν¨
μ€λ¬΄μμλ νμν λ°μ΄ν°λ§ λ½μ λ³λ API μ€νμ μ μνκ³ κ°μ²΄λ₯Ό λ§λ€μ΄μ μ€λ₯ μ 보 λ°ν
@ModelAttribute VS @RequestBody
HttpMessageConverterλ κ° νλ λ¨μλ‘ μ μ©λλ κ²μ΄ μλλΌ,μ 체 κ°μ²΄ λ¨μλ‘ μ μ©λ°λΌμ, λ©μμ§ μ»¨λ²ν° μλμ΄ μ±κ³΅ν΄μ κ°μ²΄λ₯Ό μμ±ν΄μΌ
@Valid,@Validatedκ° μ μ©
@ModelAttribute
HTTP μμ² ν리미ν°λ₯Ό μ²λ¦¬
ModelAttribute λμ κ°μ²΄λ
Setterλ©μλ νμ
νλ λ¨μλ‘ μ κ΅νκ² λ°μΈλ©μ΄ μ μ©
νΉμ νλκ° λ°μΈλ© λμ§ μλλΌλ λλ¨Έμ§ νλλ μ μ λ°μΈλ© λκ³ , @Validator λ₯Ό μ¬μ©ν κ²μ¦λ μ μ© κ°λ₯
@RequestBody
HttpMessageConverter λ¨κ³μμ JSON λ°μ΄ν°λ₯Ό κ°μ²΄λ‘ λ³κ²½ μ€ν¨νλ©΄ μ΄ν λ¨κ³κ° μ§νλμ§ μκ³ 400 Bad Request μμΈ λ°μ
νμ μ€λ₯κ° λ°μν κ²½μ° μ»¨νΈλ‘€λ¬λ νΈμΆλμ§ μκ³ , Validator λ μ μ© λΆκ°
Last updated