์ค๋ฅ ๋ฉ์์ง๋ค๋ ์ผ๊ด์ ์ผ๋ก ๊ด๋ฆฌํ๋ฉด ํธํ๋ค๐
โก๏ธ ์ด๋์๋ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์
์ง๋ ์๊ฐ์ ๋ฐฐ์ ๋ ๋ฉ์์ง ๊ธฐ๋ฅ์ ์๊ฐํด๋ณด๋ฉฐ, errors ๋ฉ์์ง ํ์ผ์ ์์ฑํด๋ณด์โ๏ธ
โก๏ธ messages.properties
๋ฅผ ์ฌ์ฉํด๋ ๋์ง๋ง, ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ฝ๊ฒ errors.properties
๋ผ๋ ๋ณ๋์ ํ์ผ๋ก ๊ด๋ฆฌํด๋ณด์.
1๏ธโฃ ์คํ๋ง ๋ถํธ ๋ฉ์์ง ์ค์ ์ถ๊ฐ - application.properties
โก๏ธ spring.messages.basename=messages,errors
2๏ธโฃ errors.properties ์ถ๊ฐ - src/main/resources/errors.properties
required.item.itemName=์ํ ์ด๋ฆ์ ํ์์
๋๋ค.
range.item.price=๊ฐ๊ฒฉ์ {0} ~ {1} ๊น์ง ํ์ฉํฉ๋๋ค.
max.item.quantity=์๋์ ์ต๋ {0} ๊น์ง ํ์ฉํฉ๋๋ค.
totalPriceMin=๊ฐ๊ฒฉ * ์๋์ ํฉ์ {0}์ ์ด์์ด์ด์ผ ํฉ๋๋ค. ํ์ฌ ๊ฐ = {1}
ValidationItemControllerV2
- addItemV3()
์ถ๊ฐ@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//๊ฒ์ฆ ๋ก์ง
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, new String[]{"required.item.itemName"}, null, null));
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null));
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, new String[]{"max.item.quantity"} ,new Object[]{9999}, null));
}
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item",new String[]{"totalPriceMin"} ,new Object[]{10000, resultPrice}, null));
}
}
//๊ฒ์ฆ์ ์คํจํ๋ฉด ๋ค์ ์
๋ ฅ ํผ์ผ๋ก
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}";
}
codes
: required.item.itemName
๋ฅผ ์ฌ์ฉํด์ ๋ฉ์์ง ์ฝ๋๋ฅผ ์ง์ ํ๋ค.arguments
: Object[]{1000, 1000000}
๋ฅผ ์ฌ์ฉํด์ ์ฝ๋์ {0}
, {1}
๋ก ์นํํ ๊ฐ์ ์ ๋ฌํ๋ค.โฌ๏ธ ์ด๋ ๊ฒ๋ง ๋ณด๋ฉด, errors.properties
์์ ์ ์ฉ๋ ๊ฑด ์ง ์ ํํ ์ ์ ์์ผ๋ฏ๋ก, ํ
์คํธ๋ฅผ ์ํด errors.properties
์ ๋ด์ฉ์ ์์ ํ ๋ค์ ํ
์คํธํด๋ณด์!
โฌ๏ธ ๋ฉ์์ง ๋ค์ test์ถ๊ฐ
โฌ๏ธ ์ ์ ์ฉ๋ ๊ฒ์ ํ์ธ๐๐ป
๊ทธ๋ฐ๋ฐ FieldError
, ObjectError
๋ ๋ค๋ฃจ๊ธฐ ๋๋ฌด ๋ฒ๊ฑฐ๋ก์ด๋ฐ,,
์ค๋ฅ ์ฝ๋๋ item.itemName
์ฒ๋ผ ์ข ๋ ์๋ํ ํ ์ ์์ง ์์๊น๐คโ
๐ก BindingResult
๊ฐ ์ ๊ณตํ๋ rejectValue()
, reject()
์ ์ฌ์ฉํ๋ฉด FieldError
, ObjectError
๋ฅผ ์ง์ ์์ฑํ์ง ์๊ณ , ๊น๋ํ๊ฒ ๊ฒ์ฆ ์ค๋ฅ๋ฅผ ๋ค๋ฃฐ ์ ์๋ค!!!!!!
ValidationItemControllerV2
- addItemV4()
์ถ๊ฐ@PostMapping("/add")
public String addItemV4(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
log.info("objectName={}", bindingResult.getObjectName());
log.info("target={}", bindingResult.getTarget());
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.rejectValue("itemName", "required");
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.rejectValue("price", "range", new Object[]{1000, 10000000}, null);
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
bindingResult.rejectValue("quantity", "max", new Object[]{9999}, null);
}
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
//๊ฒ์ฆ์ ์คํจํ๋ฉด ๋ค์ ์
๋ ฅ ํผ์ผ๋ก
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}";
}
โก๏ธ ์ด๋ ๊ฒ ์คํํ๋๋ฐ, errors.properties
์ ์๋ ์ฝ๋๋ฅผ ์ง์ ์
๋ ฅํ์ง ์์์์๋ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ ์ ์ถ๋ ฅ๋๋๋ฐ ์ด๋ป๊ฒ ๋ ๊ฒ์ผ๊นโ
๐rejectValue()
void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage)
field
: ์ค๋ฅ ํ๋๋ชerrorCode
: ์ค๋ฅ ์ฝ๋(์ด ์ค๋ฅ ์ฝ๋๋ ๋ฉ์์ง์ ๋ฑ๋ก๋ ์ฝ๋๊ฐ ์๋๊ณ , ๋ค์์ ์ค๋ช ํ messageResolver๋ฅผ ์ํ ์ค๋ฅ ์ฝ๋์ด๋ค)errorArgs
: ์ค๋ฅ ๋ฉ์์ง์์{0}
์ ์นํํ๊ธฐ ์ํ ๊ฐdefaultMessage
: ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ฐพ์ ์ ์์ ๋ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ ๋ฉ์์ง
๐ค : FieldError()
๋ฅผ ์ง์ ๋ค๋ฃฐ ๋๋ ์ค๋ฅ ์ฝ๋๋ฅผ range.item.price
์ ๊ฐ์ด ๋ชจ๋ ์
๋ ฅํ์ด! ๊ทธ๋ฐ๋ฐ rejectValue()
๋ฅผ ์ฌ์ฉํ๊ณ ๋ถํฐ๋ ์ค๋ฅ ์ฝ๋๋ฅผ range ๋ก ๊ฐ๋จํ๊ฒ ๋ฐ๊ฟจ๋๋ฐ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ์ถ๋ ฅํ๋๋ฐ,, ์ด๊ฒ ์ ๊ทธ๋ฐ ๊ฑฐ์ผ?
๐ : ์ด๊ฑธ ์ดํดํ๋ ค๋ฉด ์ผ๋จ MessageCodesResolver
๋ฅผ ๋จผ์ ์ดํดํด์ผ ํ๋จ๋ค^^
required.item.itemName
: ์ํ ์ด๋ฆ์ ํ์ ์
๋๋ค. range.item.price
: ์ํ์ ๊ฐ๊ฒฉ ๋ฒ์ ์ค๋ฅ ์
๋๋ค.required
: ํ์ ๊ฐ ์
๋๋ค.range
: ๋ฒ์ ์ค๋ฅ ์
๋๋ค.๋จ์ํ๊ฒ ๋ง๋ ๋ค๋ฉด, ๋ฒ์ฉ์ฑ์ด ์ข์ ์ฌ๋ฌ๊ณณ์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ์ง๋ง๐, ์ธ๋ฐํ๊ฒ ์์ฑํ๊ธฐ๋ ์ด๋ ต๋ค๐ฅบ
์์ธํ๊ฒ ๋ง๋ ๋ค๋ฉด, ์ธ๋ฐํ๊ฒ ์์ฑํ ์๋ ์์ง๋ง๐, ๋ฒ์ฉ์ฑ์ด ๋จ์ด์ง๋ค๐ฅบ
๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์
โ๏ธ๋ฒ์ฉ์ฑ์ผ๋ก ์ฌ์ฉํ๋ค๊ฐ, ์ธ๋ฐํ๊ฒ ์์ฑํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ ์ธ๋ฐํ ๋ด์ฉ์ด ์ ์ฉ๋๋๋ก ๋ฉ์์ง์ ๋จ๊ณ๋ฅผ ๋๋ ๋ฐฉ๋ฒโ๏ธ์ด๋ค
์๋ฅผ ๋ค์ด
#Level1
required.item.itemName: ์ํ ์ด๋ฆ์ ํ์ ์
๋๋ค.
#Level2
required: ํ์ ๊ฐ ์
๋๋ค.
์ด์ ๊ฐ์ด, required
๋ผ๋ ์ค๋ฅ ์ฝ๋๋ฅผ ์ฌ์ฉํ ๋,
1๏ธโฃ required
๋ง ์๋ค๋ฉด "ํ์ ๊ฐ ์
๋๋ค" ๋ฅผ ์ฌ์ฉํ๊ณ ,
2๏ธโฃ required.item.itemName
์ ๊ฐ์ด ๊ฐ์ฒด๋ช
๊ณผ ํ๋๋ช
์ ์กฐํฉํ ์ธ๋ฐํ ๋ฉ์์ง ์ฝ๋๊ฐ ์๋ค๋ฉด "์ํ ์ด๋ฆ์ ํ์์
๋๋ค" ๋ผ๋ ๋ฉ์์ง๋ฅผ ๋์ ์ฐ์ ์์๋ก ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
โก๏ธ ์คํ๋ง์ ์ด๋ฌํ ๊ธฐ๋ฅ์ MessageCodesResolver
๋ก ์ง์ํ๋ค ๐๐ป
MessageCodesResolverTest
package hello.itemservice.validation;
import org.junit.jupiter.api.Test;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.MessageCodesResolver;
import static org.assertj.core.api.Assertions.*;
public class MessageCodesResolverTest {
MessageCodesResolver codesResolver = new DefaultMessageCodesResolver();
@Test
// ๊ฐ์ฒด ์๋ฌ
void messageCodesResolverObject() {
String[] messageCodes = codesResolver.resolveMessageCodes("required", "item");
assertThat(messageCodes).containsExactly("required.item", "required");
}
@Test
// ํ๋ ์๋ฌ
void messageCodesResolverField() {
String[] messageCodes = codesResolver.resolveMessageCodes("required", "item", "itemName", String.class);
assertThat(messageCodes).containsExactly(
"required.item.itemName",
"required.itemName",
"required.java.lang.String",
"required"
);
}
}
โฌ๏ธ ํ ์คํธ ๊ฒฐ๊ณผ ๐๐ป
MessageCodesResolver
MessageCodesResolver
์ธํฐํ์ด์ค์ด๊ณ DefaultMessageCodesResolver
๋ ๊ธฐ๋ณธ ๊ตฌํ์ฒด์ด๋ค.ObjectError
, FieldError
๊ณผ ํจ๊ป ์ฌ์ฉํ๋ค.1๏ธโฃ ๊ฐ์ฒด ์ค๋ฅ
๊ฐ์ฒด ์ค๋ฅ์ ๊ฒฝ์ฐ ๋ค์ ์์๋ก 2๊ฐ์ง ์์ฑ
1.: code + "." + object name
2.: code
์) ์ค๋ฅ ์ฝ๋: required, object name: item
1.: required.item
2.: required
โก๏ธ ObjectError reject("totalPriceMin")
๋ค์ 2๊ฐ์ง ์ค๋ฅ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ฑ
totalPriceMin.item
totalPriceMin
2๏ธโฃ ํ๋ ์ค๋ฅ
ํ๋ ์ค๋ฅ์ ๊ฒฝ์ฐ ๋ค์ ์์๋ก4๊ฐ์ง ๋ฉ์์ง ์ฝ๋ ์์ฑ
1.: code + "." + object name + "." + field
2.: code + "." + field
3.: code + "." + field type
4.: code
์) ์ค๋ฅ ์ฝ๋: typeMismatch, object name "user", field "age", field type: int
1. "typeMismatch.user.age"
2. "typeMismatch.age"
3. "typeMismatch.int"
4. "typeMismatch
โก๏ธ FieldError rejectValue("itemName", "required")
๋ค์ 4๊ฐ์ง ์ค๋ฅ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ฑ
required.item.itemName
required.itemName
required.java.lang.String
required
3๏ธโฃ ๋์ ๋ฐฉ์
rejectValue()
, reject()
๋ ๋ด๋ถ์์ MessageCodesResolver
๋ฅผ ์ฌ์ฉํ๋ค. ์ฌ๊ธฐ์์ ๋ฉ์์ง ์ฝ๋๋ค์ ์์ฑํ๋ค.FieldError
, ObjectError
์ ์์ฑ์๋ฅผ ๋ณด๋ฉด, ์ค๋ฅ ์ฝ๋๋ฅผ ํ๋๊ฐ ์๋๋ผ ์ฌ๋ฌ ์ค๋ฅ ์ฝ๋๋ฅผ ๊ฐ์ง ์ ์๋ค. MessageCodesResolver
๋ฅผ ํตํด์ ์์ฑ๋ ์์๋๋ก ์ค๋ฅ ์ฝ๋๋ฅผ ๋ณด๊ดํ๋ค.4๏ธโฃ ์ค๋ฅ ๋ฉ์์ง ์ถ๋ ฅ
th:errors
๊ฐ ์คํ๋๋ค. ํต์ฌ์ ๊ตฌ์ฒด์ ์ธ ๊ฒ์์โ๏ธ ๋ ๊ตฌ์ฒด์ ์ธ ๊ฒ์ผ๋กโ๏ธ
MessageCodesResolver
๋ required.item.itemName
์ฒ๋ผ ๊ตฌ์ฒด์ ์ธ ๊ฒ์ ๋จผ์ ๋ง๋ค์ด์ฃผ๊ณ , required
์ฒ๋ผ ๋ ๊ตฌ์ฒด์ ์ธ ๊ฒ์ ๊ฐ์ฅ ๋์ค์ ๋ง๋ ๋ค.
โก๏ธ ์ด๋ ๊ฒ ํ๋ฉด ์์ ๋งํ ๊ฒ ์ฒ๋ผ ๋ฉ์์ง์ ๊ด๋ จ๋ ๊ณตํต ์ ๋ต์ ํธ๋ฆฌํ๊ฒ ๋์
ํ ์ ์๋ค๐๐ป
errors.properties
์์ #required.item.itemName=์ํ ์ด๋ฆ์ ํ์์
๋๋ค.
#range.item.price=๊ฐ๊ฒฉ์ {0} ~ {1} ๊น์ง ํ์ฉํฉ๋๋ค.
#max.item.quantity=์๋์ ์ต๋ {0} ๊น์ง ํ์ฉํฉ๋๋ค.
#totalPriceMin=๊ฐ๊ฒฉ * ์๋์ ํฉ์ {0}์ ์ด์์ด์ด์ผ ํฉ๋๋ค. ํ์ฌ ๊ฐ = {1}
#==ObjectError==
#Level1
totalPriceMin.item=์ํ์ ๊ฐ๊ฒฉ * ์๋์ ํฉ์ {0}์ ์ด์์ด์ด์ผ ํฉ๋๋ค. ํ์ฌ ๊ฐ = {1}
#Level2 - ์๋ต
totalPriceMin=์ ์ฒด ๊ฐ๊ฒฉ์ {0}์ ์ด์์ด์ด์ผ ํฉ๋๋ค. ํ์ฌ ๊ฐ = {1}
#==FieldError==
#Level1
required.item.itemName=์ํ ์ด๋ฆ์ ํ์์
๋๋ค.
range.item.price=๊ฐ๊ฒฉ์ {0} ~ {1} ๊น์ง ํ์ฉํฉ๋๋ค.
max.item.quantity=์๋์ ์ต๋ {0} ๊น์ง ํ์ฉํฉ๋๋ค.
#Level2 - ์๋ต
#Level3
required.java.lang.String = ํ์ ๋ฌธ์์
๋๋ค.
required.java.lang.Integer = ํ์ ์ซ์์
๋๋ค.
min.java.lang.String = {0} ์ด์์ ๋ฌธ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
min.java.lang.Integer = {0} ์ด์์ ์ซ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
range.java.lang.String = {0} ~ {1} ๊น์ง์ ๋ฌธ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
range.java.lang.Integer = {0} ~ {1} ๊น์ง์ ์ซ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.
max.java.lang.String = {0} ๊น์ง์ ์ซ์๋ฅผ ํ์ฉํฉ๋๋ค.
max.java.lang.Integer = {0} ๊น์ง์ ์ซ์๋ฅผ ํ์ฉํฉ๋๋ค.
#Level4
required = ํ์ ๊ฐ ์
๋๋ค.
min= {0} ์ด์์ด์ด์ผ ํฉ๋๋ค.
range= {0} ~ {1} ๋ฒ์๋ฅผ ํ์ฉํฉ๋๋ค.
max= {0} ๊น์ง ํ์ฉํฉ๋๋ค.
ํฌ๊ฒ ๊ฐ์ฒด ์ค๋ฅ์ ํ๋ ์ค๋ฅ๋ฅผ ๋๋์๊ณ , ๋ฒ์ฉ์ฑ์ ๋ฐ๋ผ ๋ ๋ฒจ์ ๋๋์ด๋์๋ค!
itemName
์ ๊ฒฝ์ฐ required
๊ฒ์ฆ ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋ฐ์ํ๋ฉด ๋ค์ ์ฝ๋ ์์๋๋ก ๋ฉ์์ง๊ฐ ์์ฑ๋๋ค!
1๏ธโฃ required.item.itemName
2๏ธโฃ required.itemName
3๏ธโฃ required.java.lang.String
4๏ธโฃ required
์ด๋ ๊ฒ ์์ฑ๋ ๋ฉ์์ง ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์๋๋ก MessageSource
์์ ๋ฉ์์ง์์ ์ฐพ๋๋ฐ,
๊ตฌ์ฒด์ ์ธ ๊ฒ์์ ๋ ๊ตฌ์ฒด์ ์ธ ์์๋๋ก ์ฐพ๋๋คโ๏ธ
1๏ธโฃ Level1,2 ์ ๋ถ ์ฃผ์ํด๋ณด์
2๏ธโฃ Level3 ์ ๋ถ ์ฃผ์ํด๋ณด์
3๏ธโฃ Level4 ์ ๋ถ ์ฃผ์ํด๋ณด์ โก๏ธ ๋ชป์ฐพ์ผ๋ฉด ์ฝ๋์ ์์ฑํ ๋ํดํธ ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ๋ค.
4๏ธโฃ Object ์ค๋ฅ๋ Level1, Level2๋ก ์ฌํ์ฉ ๊ฐ๋ฅํ๋ค.
๊ฒ์ฆ ์ค๋ฅ ์ฝ๋๋ โฌ๏ธ์๋์ ๊ฐ์ดโฌ๏ธ 2๊ฐ์ง๋ก ๋๋ ์ ์๋ค.
1๏ธโฃ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ค์ ํ ์ค๋ฅ ์ฝ๋ โก๏ธ rejectValue()
๋ฅผ ์ง์ ํธ์ถ
2๏ธโฃ ์คํ๋ง์ด ์ง์ ๊ฒ์ฆ ์ค๋ฅ์ ์ถ๊ฐํ ๊ฒฝ์ฐ(์ฃผ๋ก ํ์
์ ๋ณด๊ฐ ๋ง์ง ์์)
์ง๊ธ๊น์ง ํ์ตํ ๋ฉ์์ง ์ฝ๋ ์ ๋ต์ ๊ฐ์ ์ ํ์ธํด๋ณด์!
1๏ธโฃ ์ํ ์
๋ ฅ ํผ price
ํ๋์ ์ซ์๊ฐ ์๋ 'A'๋ฅผ ๋ฃ์ด๋ณด์
2๏ธโฃ ๋ก๊ทธ๋ฅผ ํ์ธํด๋ณด๋ฉด BindingResult
์ FieldError
๊ฐ ๋ด๊ฒจ์๊ณ , ๋ค์๊ณผ ๊ฐ์ ๋ฉ์์ง ์ฝ๋๋ค์ด ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
typeMismatch.item.price
typeMismatch.price
typeMismatch.java.lang.Integer
typeMismatch
3๏ธโฃ ์คํ๋ง์ ํ์
์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด typeMismatch
๋ผ๋ ์ค๋ฅ ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ค.
์ด ์ค๋ฅ ์ฝ๋๊ฐ MessageCodesResolver
๋ฅผ ํตํ๋ฉด์ 4๊ฐ์ง ๋ฉ์์ง ์ฝ๋๊ฐ ์์ฑ๋ ๊ฒ์ด๋ค.
4๏ธโฃ error.properties
์ ๋ค์ ๋ด์ฉ์ ์ถ๊ฐํ์!
#์ถ๊ฐ
typeMismatch.java.lang.Integer=์ซ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์. typeMismatch=ํ์
์ค๋ฅ์
๋๋ค.
5๏ธโฃ ๋ค์ ์คํํด๋ณด๋ฉด, ์์ค์ฝ๋๋ฅผ ํ๋๋ ๊ฑด๋ค์ง ์๊ณ , ์ํ๋ ๋ฉ์์ง๋ฅผ ๋จ๊ณ๋ณ๋ก ์ค์ ํ ์ ์๋ค!
๋ณต์กํ ๊ฒ์ฆ ๋ก์ง์ ๋ณ๋๋ก ๋ถ๋ฆฌํด๋ณด์๐
โก๏ธValidator
๋ผ๋ ๊ฒ์ฆ ๋ก์ง์ ์ ์ฉ์ผ๋ก ๋ชจ์๋๋ ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด์ ๋ถ๋ฆฌํ์!
package hello.itemservice.web.validation;
import hello.itemservice.domain.item.Item;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target;
if (!StringUtils.hasText(item.getItemName())) {
errors.rejectValue("itemName", "required");
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
errors.rejectValue("price", "range", new Object[]{1000, 10000000}, null);
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
errors.rejectValue("quantity", "max", new Object[]{9999}, null);
}
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
}
}
๐ ์คํ๋ง์ ๊ฒ์ฆ์ ์ฒด๊ณ์ ์ผ๋ก ์ ๊ณตํ๊ธฐ ์ํด ์๋์ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ค
public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); }
supports() {}
: ํด๋น ๊ฒ์ฆ๊ธฐ๋ฅผ ์ง์ํ๋ ์ฌ๋ถ ํ์ธvalidate(Object target, Errors errors)
: ๊ฒ์ฆ ๋์ ๊ฐ์ฒด์BindingResult
@PostMapping("/add")
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
// target : item
// errors : bindingResult
itemValidator.validate(item, bindingResult);
// ๊ฒ์ฆ์ ์คํจํ๋ฉด ๋ค์ ์
๋ ฅ ํผ์ผ๋ก
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}";
}
๐ก
ItemValidator
๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์ฃผ์ ๋ฐ์์ ์ง์ ํธ์ถํ์!
โฌ๏ธ ์ ์์ ์ผ๋ก ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธ๐๐ป ๊ฒ์ฆ๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ด ๊น๋ํ๊ฒ ๋ถ๋ฆฌ๋์๋ค!
์คํ๋ง์ด Validator
์ธํฐํ์ด์ค๋ฅผ ๋ณ๋๋ก ์ ๊ณตํ๋ ์ด์ ๋ ์ฒด๊ณ์ ์ผ๋ก ๊ฒ์ฆ ๊ธฐ๋ฅ์ ๋์
ํ๊ธฐ ์ํด์ ์ด๋ค!
๊ทธ๋ฐ๋ฐ ๋ฐ๋ก ์์์๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ์ง์ ๋ถ๋ฌ์ ์ฌ์ฉํ๋ค.
์ด๋ ๊ฒ ๊ทธ๋~๋ก ์ฌ์ฉํด๋ ๋์ง๋ง, Validator
์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํด์ ๊ฒ์ฆ๊ธฐ๋ฅผ ๋ง๋ค๋ฉด ์คํ๋ง์ ์ถ๊ฐ์ ์ธ ๋์์ ๋ฐ์ ์ ์๋ค๐ฎ
@InitBinder
public void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
๐ก WebDataBinder๋ฅผ ํตํด์ ์ฌ์ฉํ๊ธฐ
โก๏ธ WebDataBinder
๋ ์คํ๋ง์ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ์ญํ ์ ํด์ฃผ๊ณ ๊ฒ์ฆ ๊ธฐ๋ฅ๋ ๋ด๋ถ์ ํฌํจํ๋ค!!
์ด์ฒ๋ผ WebDataBinder
์ ๊ฒ์ฆ๊ธฐ๋ฅผ ์ถ๊ฐํ๋ฉด ํด๋น ์ปจํธ๋กค๋ฌ์์๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ์๋์ผ๋ก ์ ์ฉํ ์ ์๋ค๐๐ป
(@InitBinder
ํด๋น ์ปจํธ๋กค๋ฌ์๋ง ์ํฅ์ ์ค๋ค)
@PostMapping("/add")
// @Validated ์ ๋
ธํ
์ด์
์ถ : Item์ ๋ํด ์๋์ผ๋ก ๊ฒ์ฆ๊ธฐ๊ฐ ์คํ๋จ
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
// ๊ฒ์ฆ์ ์คํจํ๋ฉด ๋ค์ ์
๋ ฅ ํผ์ผ๋ก
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}";
}
validator
๋ฅผ ์ง์ ํธ์ถํ๋ ๋ถ๋ถ์ด ์ฌ๋ผ์ง๊ณ , ๋์ ์ ๊ฒ์ฆ ๋์ ์์ @Validated
๋ผ๋ ์ ๋
ธํ
์ด์
์ด ๋ถ์๋คโ๏ธ
@Validated
๋ ๊ฒ์ฆ๊ธฐ๋ฅผ ์คํํ๋ผ๋ ์ ๋
ธํ
์ด์
์ด๋ค.WebDataBinder
์ ๋ฑ๋กํ ๊ฒ์ฆ๊ธฐ๋ฅผ ์ฐพ์์ ์คํํ๋ค. โฌ๏ธ ์ ์์ ์ผ๋ก ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธ ๐๐ป
๊ฒ์ฆ2 ๊น์ง๋ง ํ๋๋ฐ ์ ํผ์ ๋ฒ์ 6 ๊น์ง ์๋ค๋จ,,๐