Spring Bean Validation#2 ์คํ๋ง ๋น ๊ฒ์ฆ
by JiwonDev์ด ๊ธ์ ์๋๊ธ๊ณผ ์ด์ด์ง๋ ๊ธ์ด๋ค.
2021.08.29 - [Backend/Spring MVC] - Spring Vaildation#1 ๊ฒ์ฆ
# Bean Validation
WebDataBinder๋ฅผ ์ฌ์ฉํ๋ค ํ๋ค ์์์ ์ค๋ช ํ Validation ๊ฐ์ฒด๋ฅผ ๋งค๋ฒ ์์ฑํ๋๊ฑด ๋ฒ๊ฑฐ๋กญ๋ค. ์ด๋ฅผ ์ด๋ ธํ ์ด์ ๊ธฐ๋ฐ์ผ๋ก ์๋์ผ๋ก ๋๊ฒ ๋ง๋ค์๋ ์์๊น? → BeanValidation 2.0 (JSR-380)
@Data // @Get, @Set, @toStr, @Equals&Hash, @RequiredArg
public class Item {
@NotNull(groups = UpdateCheck.class) //์์ ์๊ตฌ์ฌํญ ์ถ๊ฐ
private Long id;
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Range(min = 1000, max = 1000000, groups = {SaveCheck.class, UpdateCheck.class})
private Integer price;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Max(value = 9999, groups = {SaveCheck.class})
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
Bean Validation์ ๊ฒ์ฆ ๊ตฌํ์ฒด๊ฐ ์๋๋ผ, ์ด๋ ธํ ์ด์ ๊ณผ ์ฌ๋ฌ ์ธํฐํ์ด์ค๋ค์ ๋ชจ์์ด๋ค. ๋ง์น JPA - Hinbernate์ ๊ด๊ณ์ฒ๋ผ ๋์ํ๋ฉฐ ํ์์๋ฐ๋ผ ๊ตฌํ์ฒด๋ฅผ ๋ฐ๊ฟ์ ์ฌ์ฉํ ์ ์๋ค. ๋ณดํต ๋๋ถ๋ถ Hinbernate Validator ๊ตฌํ์ฒด๋ฅผ ๋ง์ด ์ฌ์ฉํ๋๋ฐ, ํ์ด๋ฒ๋ค์ดํธ ์ฌ๋จ์์ ๋ง๋ค์ด์ ๋ถ์ ์ด๋ฆ์ด๋ค. JPA๋ ๊ธฐ์ ์ ์ธ ์ฐ๊ด์ ์๋ค.
๋ค๋ง ํ์ค ์ธํฐํ์ด์ค (javax.validation.constraints)์์ ์ ๊ณตํ์ง ์๋ ๊ธฐ๋ฅ์ ํ์ด๋ฒ๋ค์ดํธ Validator๊ฐ ์ถ๊ฐ๋ก ์ ๊ณตํด์ฃผ๊ธฐ๋ ํ๋ค. (org.hibernate.validator.constraints)
์ฌ์ฉํ๋ ๊ฒ์ฆ ์ ๋ ธํ ์ด์ ๋ค์ ๊ณต์๋ฌธ์๋ง ๋ด๋ ์ ์ ์๋๋ก ์ง๊ด์ ์ด๋ค. ํ๋ฒ ์ฝ์ด๋ณด๋๋ก ํ์.
- ๊ณต์ ์ฌ์ดํธ: http://hibernate.org/validator/
- ๊ฒ์ฆ ์ ๋ ธํ ์ด์ ๋ชจ์: https://docs.jboss.org/hibernate/validator/6.2/reference
- ๊ณต์ ๋ฉ๋ด์ผ: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
# ๊ฒฐ๊ตญ์๋ Validtor๋ก ๋์ํ๋ค.
์คํ๋ง์ ๊ธฐ๋ฅ์ ์ด์ฉํด์ ์ด๋ ธํ ์ด์ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉํ์ง๋ง, ๋ด๋ถ์ ์ผ๋ก๋ ์ ๋ฒ ๊ธ์์ ๋ฐฐ์ ๋ Validtor ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ๋ค. ์๋ ์ฝ๋๋ฅผ ์ง์ ๊บผ๋ด ์ฌ์ฉํ ์ผ์ ์์ง๋ง ๊ถ๊ธํ๋ค๋ฉด ์๋์ ๊ฐ์ด ํ ์คํธํด๋ด๋ ๋๋ค.
@Test
void beanValidation() {
// ์ด ์ฝ๋๋ฅผ ์คํ๋ง์์ ์ง์ ์ฌ์ฉํ ์ผ์ ์์ผ๋ ๊ฑฑ์ ๋ง์.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Item item = new Item();
item.setQuantity(10000);
Set<ConstraintViolation<Item>> violations = validator.validate(item);
for (ConstraintViolation<Item> violation : violations) {
System.out.println("violation = " + violation);
System.out.println("violation = " + violation.getMessage());
}
}
# Bean Validation์๋ด๋ถ๋์
์คํ๋ง๋ถํธ ํ์ค์ด๋ฉด ๋๋ค. implementation 'org.springframework.boot:spring-boot-starter-validation'
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ LocalValidatorFactoryBean ์ ๊ธ๋ก๋ฒ Validator ๊ฐ์ฒด๋ก ๋ฑ๋กํ๋ค. ์ด ๊ฐ์ฒด๊ฐ ์ด๋ ธํ ์ด์ (@NotNull)์ ๋ฐ๋ผ ๊ฒ์ฆ์ ์ํํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๋ ์๋ฌด๋ฐ ์ถ๊ฐ์์ ์์ด ๊ฒ์ฆํ๋ ค๋ ๊ณณ์ @Valid, @Validated๋ง ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
//๋ชจ๋ ์ปจํธ๋กค๋ฌ(๊ธ๋ก๋ฒ)๋ก ๋ฑ๋ก๋์๊ธฐ ๋๋ฌธ์, ์ด๋ฐ ์ฝ๋๋ ๋ ์ด์ ํ์์๋ค.
//@InitBinder
//public void init(WebDataBinder dataBinder) {
// log.info("init binder {}", dataBinder);
// dataBinder.addValidators(itemValidator);
//}
@PostMapping("/add") // @Validated๋ง ๋ถ์ด๋ฉด ์ ์ฉ์๋ฃ.
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult) {
}
์ฐ๋ฆฌ๊ฐ ํ๋๊ฑด ์๋ฌด๊ฒ๋ ์์ง๋ง, ๋ด๋ถ์์ Bean Validation์ ์๋์ ๊ฐ์ด ๋์ํ๋ค.
- ์ฑ์ด ์์ํ ๋ BeanVaildaton(๊ตฌํ์ฒด - LocalVaildatorFactoryBean)๊ฐ ์ ์ญ์ผ๋ก ์ปจํธ๋กค๋ฌ์ ์ ์ฉ๋๋ค.
- @ModelAttribute ๊ฐ๊ฐ ํ๋์ ํ์
๋ณํ(๋ฐ์ธ๋ฉ)์ ์๋ํ๋ค.
- ๋ง์ฝ ํ์ ๋ฐ์ธ๋ฉ์ ์คํจํ๋ฉด FieldError('typeMismatch') ๋ฅผ ์์ฑ - ํ์
๋ฐ์ธ๋ฉ์ ์ฑ๊ณตํ๋ค๋ฉด Validator๋ฅผ ์ ์ฉ. (LocalValidatorFactoryBean๊ฐ @์ด๋
ธํ
์ด์
์ Validator๋ก ๋ณํ)
- ์ค๋ฅ์ฝ๋๊ฐ ์ ๋ ธํ ์ด์ ์ด๋ฆ์ผ๋ก ์ ์ฉ๋๋ค. - ํด๋น ์ค๋ฅ์ฝ๋์ ํด๋นํ๋ ๋ฉ์์ง๋ฅผ MessageCodeResolver๋ฅผ ์ด์ฉํ์ฌ ์ฐพ๋๋ค.
์ฐ๋ฆฌ๊ฐ ํ ์ผ์ errors.properties๋ง ์์ฑํด์ฃผ๋ฉด ๋์ด๋ค.
- ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ชป์ฐพ์๋ค๋ฉด @NotBlank(message = "๊ธฐ๋ณธ๊ฐ")์ ์ฐ๊ณ , ๊ฑฐ๊ธฐ๋ ์๋ค๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํ๋ค.
# errors.properties
# Bean Validation ์ถ๊ฐ
NotBlank.item.itemNmae = ์ํ ์ด๋ฆ์ ์ ์ด์ฃผ์ธ์.
NotBlank={0} ๊ณต๋ฐฑX
Range={0}, {2} ~ {1} ํ์ฉ
Max={0}, ์ต๋ {1}
# ๊ฐ์ฒด (Object) ์ค๋ฅ ์ฒ๋ฆฌํ๊ธฐ
๋๋ฉ์ธ ๊ฐ์ฒด์ ์ ๋ ธํ ์ด์ ์ ์ง์ ๊ฒ์ฆ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ๋ ์์ผ๋, ์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ ์๋๋ค.
@Data // ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๊ฒ์ฆ.
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >=
10000")
public class Item {
//...
}
๊ฐ์ฒด ์ค๋ฅ ์ฒ๋ฆฌ (ObjectError)๊ฐ ํ์ํ๋ค๋ฉด ์๋์ ๊ฐ์ด ์ปจํธ๋กค๋ฌ์ ์ฝ๋๋ก ์ฒ๋ฆฌํ๋๊ฒ ๋์ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes, Model model) {
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
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/v3/addForm";
}
//์ฑ๊ณต ๋ก์ง
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v3/items/{itemId}";
}
# Bean Validation - ๊ฐ๊ฐ ๋ค๋ฅธ ๊ฒ์ฆ ์ ์ฉํ๊ธฐ
๋ค์๊ณผ ๊ฐ์ ์๊ตฌ์ฌํญ์ด ์๋ค๊ณ ์๊ฐํด๋ณด์.
- ํ์
๊ฒ์ฆ
- ๊ฐ๊ฒฉ, ์๋์ ๋ฌธ์๊ฐ ๋ค์ด๊ฐ์์ผ๋ฉด ๊ฒ์ฆ ์ค๋ฅ ์ฒ๋ฆฌ - ํ๋ ๊ฒ์ฆ
- ์ํ๋ช : ํ์. ๊ณต๋ฐฑ ๋ถ๊ฐ๋ฅ
- ๊ฐ๊ฒฉ: ์ต์ 1000์, 1๋ฐฑ๋ง์ ์ดํ
- ์๋: ์ต๋ 9999๊ฐ - ๋ณตํฉ์ ์ธ ๊ฒ์ฆ (ObjectError)
- ์ต์์ฃผ๋ฌธ๊ธ์ก: ๊ฐ๊ฒฉ * ์๋์ ํฉ์ 10000์ ์ด์์ด์ด์ผ ๊ฑฐ๋๊ฐ ์น์ธ๋จ.
ํ์ง๋ง ์ฐ๋ฆฌ ๊ธฐํ์๋ ๋ฑ๋ก๋ง๊ณ ์์ ๋๋ ์๋์ ๊ฐ์ ๋ค๋ฅธ ์๊ตฌ์ฌํญ์ ์ ์ฉํ๊ณ ์ถ๋ค๊ณ ํ๋ค.
- ๋ฑ๋ก๋ง๊ณ ์์ ์์๋ ์๋์ ์ ํ์ด ์๋ค.
- ๋ฑ๋ก์ ์๊ด์์ง๋ง ์์ ์์๋ id๊ฐ์ด ํ์์ด๋ค.
๊ทธ๋ฌ๋ฉด ์ฝ๋๋ฅผ ์๋์ ๊ฐ์ด ์์ ํ๋ฉด ๋ ๊น?
@Data // @Get, @Set, @toStr, @Equals&Hash, @RequiredArg
public class Item {
@NotNull //์์ ์๊ตฌ์ฌํญ ๋ฐ์ - NotNull ์ถ๊ฐ
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
// @Max(value = 9999) ์์ ์๊ตฌ์ฌํญ ๋ฐ์- MAX ์ญ์
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
๋น์ฐํ ์ด๋ฌ๋ฉด ๋ฑ๋ก์์ ๋ฌธ์ ๊ฐ ์๊ธด๋ค. ๊ทธ๋ ๋ค๋ฉด ๋ฑ๋ก, ์์ ์ ๊ฐ๊ฐ ๋ค๋ฅธ ๊ฒ์ฆ์ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
@ ์ ํ์ง1 - BeanValidation groups
๊ทธ๋ฃน์ ์ง์ ํ ์ ์๋ค. ์ฌ์ฉํ ๋๋ @Vaildated(value = Group.class) ์ฐธ๊ณ ๋ก @Vaild์๋ ์๋ ๊ธฐ๋ฅ์ด๋ค.
ํ์ง๋ง ์ค๋ฌด์์๋ ์ ์ฌ์ฉํ์ง ์๋ ๊ธฐ๋ฅ์ด๋ค. ๊ทธ ์ด์ ๋ @ ์ ํ์ง2 ๋ฅผ ๋ณด์.
package hello.itemservice.domain.item;
public interface SaveCheck {// ์ ์ฅ์ฉ ๊ทธ๋ฃน
}
package hello.itemservice.domain.item;
public interface UpdateCheck {// ์์ ์ฉ ๊ทธ๋ฃน
}
@Data // @Get, @Set, @toStr, @Equals&Hash, @RequiredArg
public class Item {
@NotNull(groups = UpdateCheck.class) //์์ ์๊ตฌ์ฌํญ ์ถ๊ฐ
private Long id;
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Range(min = 1000, max = 1000000, groups = {SaveCheck.class, UpdateCheck.class})
private Integer price;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Max(value = 9999, groups = {SaveCheck.class})
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
์ด๋ ๊ฒ ์ ํ ๊ทธ๋ฃน์ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
@PostMapping("/{itemId}/edit")
public String editV2(@PathVariable Long itemId,
@Validated(UpdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {...}
@PostMapping("/add")
public String addItem2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {...}
@ ์ ํ์ง2 - ๋ฑ๋ก์ฉ ํผ, ์์ ์ฉ ํผ ๊ฐ์ฒด๋ถ๋ฆฌ
์์ ๊ฐ์ด groups๋ก ๊ตฌ๋ถํ๊ธฐ์๋ ์ฝ๋๊ฐ ๋๋ฌด ์ง์ ๋ถํด์ง๋ค. ๋ํ ์ค์ ์๋น์ค์์๋ Item ๋๋ฉ์ธ ๊ฐ์ฒด์ ์ ๋ณด ๋ง๊ณ ๋ ์ฝ๊ด ์ ๋ณด, ๋ถ๊ฐ๋ฐ์ดํฐ๋ค์ DB์์ ์ถ๊ฐ๋ก ๋ฐ๋๋ฐ ์ด๊ฒ๋ค์ ๊ฐ์ด ๊ฒ์ฆํ ์ ์๋ค.
๊ทธ๋์ ์ค๋ฌด์์๋ ItemSaveForm, ItemUpdateForm ์ฒ๋ผ ์ ์ก ๊ฐ์ฒด๋ฅผ ๋ฐ๋ก ๋ง๋ ๋ค.
- (๊ธฐ์กด์ ๋ฐฉ๋ฒ) [HTML Form -> Item -> Controller -> Item -> Repository]
- ์ฅ์ : ๊ณผ์ ์ด ๊ฐ๋จํ๋ค.
- ๋จ์ : ์์ ์ ๊ฒ์ฆ์ด ์ค๋ณต๋ ์ ์๊ณ , groups๋ก ๋ณต์กํ ์ฝ๋๊ฐ ๋์จ๋ค. - (ํผ ๊ฐ์ฒด ์ฌ์ฉ) [HTML Form -> ItemSaveForm -> Controller -> Item ์์ฑ -> Repository]
- ์ฅ์ : ๋ฐ์ดํฐ๊ฐ ๋ณต์กํด๋, ๋ณ๋์ Form ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๊ธฐ์ ๊ด๋ฆฌํ๊ธฐ ์ฝ๋ค. ์ค๋ณต์ ๋ฐฉ์งํ๋ค.
- ๋จ์ : ํผ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ปจํธ๋กค๋ฌ์์ Item ๊ฐ์ฒด๋ฅผ ์์ฑ & ๋ฐํํด์ผํ๋ค.
์ฐธ๊ณ ๋ก ๋น์ทํ๋ค๊ณ ํด์ ๋ฑ๋ก, ์์ ์ฉ HTML ํ ํ๋ฆฟ์ ํฉ์ณ์ ์ฌ์ฉํ๋ค๋ฉด ๋์ค์ ์ ์ง๋ณด์์์ ๊ณ ํต๋ฐ์ ์ ์๋ค.
@Data // Item์ ๊ฒ์ฆ์ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก, ๊ฒ์ฆ ์ฝ๋๋ฅผ ์ ๊ฑฐํด์ฃผ์
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
}
// ์ฐธ๊ณ ๋ก form์ ์ปจํธ๋กค๋ฌ์๋ง ์ฐ๋ ๊ฐ์ฒด๋ผ ํ๋ก์ ํธ๊ฒฝ๋ก/web/form/... ์ ๋ง๋ ๋ค.
@Data
public class ItemSaveForm {
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(value = 9999)
private Integer quantity;
}
@Data
public class ItemUpdateForm {
@NotNull
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
//์์ ์์๋ ์๋์ ์์ ๋กญ๊ฒ ๋ณ๊ฒฝํ ์ ์๋ค.
private Integer quantity;
}
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์์ ์๋์ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋๋ค.
- ๋จ ๋ทฐ์์ ๋ชจ๋ธ ์ด๋ฆ์ item์ผ๋ก ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด @ModelAttribute("item")์ผ๋ก ์ง์ ํด์ค์ผํ๋ค.
@Slf4j
@Controller
@RequestMapping("/validation/v4/items")
@RequiredArgsConstructor
public class ValidationItemControllerV4 {
private final ItemRepository itemRepository;
@GetMapping("/{itemId}") // prg -> post redirect get
public String item(@PathVariable long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "validation/v4/item";
}
...
@PostMapping("/add") // ๋ฑ๋ก์๋ ItemSaveForm ์ฌ์ฉ
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
if (form.getPrice() != null && form.getQuantity() != null) {
int resultPrice = form.getPrice() * form.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
//๊ฒ์ฆ์ ์คํจํ๋ฉด ๋ค์ ์
๋ ฅ ํผ์ผ๋ก
if (bindingResult.hasErrors()) {
log.info("errors={} ", bindingResult);
return "validation/v4/addForm";
}
//์ฑ๊ณต ๋ก์ง, ํผ์์ ๊ฐ์ ธ์์ Item์ ์์ฑํจ
Item item = new Item();
item.setItemName(form.getItemName());
item.setPrice(form.getPrice());
item.setQuantity(form.getQuantity());
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v4/items/{itemId}";
}
@PostMapping("/{itemId}/edit") // ์์ ์๋ ItemUpdateForm ์ฌ์ฉ
public String edit(@PathVariable Long itemId,
@Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {
//ํน์ ํ๋๊ฐ ์๋ ๋ณตํฉ ๋ฃฐ ๊ฒ์ฆ
if (form.getPrice() != null && form.getQuantity() != null) {
int resultPrice = form.getPrice() * form.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v4/editForm";
}
Item itemParam = new Item();
itemParam.setItemName(form.getItemName());
itemParam.setPrice(form.getPrice());
itemParam.setQuantity(form.getQuantity());
itemRepository.update(itemId, itemParam);
return "redirect:/validation/v4/items/{itemId}";
}
}
# Bean Validation - HTTP ๋ฉ์์ง ์ปจ๋ฒํฐ (API @RequestBody)
- @ModelAttribute ๋ HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ(URL ์ฟผ๋ฆฌ ์คํธ๋ง, POST Form)๋ฅผ ๋ค๋ฃฐ ๋ ์ฌ์ฉํ๋ค.
- @RequestBody ๋ HTTP Body์ ๋ฐ์ดํฐ ์์ฒด๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํ ๋ ์ฌ์ฉํ๋ค. (API - JSON ์์ฒญ)
ํฌ๊ฒ ๋ฌ๋ผ์ง๋๊ฑด ์๋ค. ๊ทธ๋ฅ @ModelAttribute๊ฐ @RequestBody๋ก ๋ฐ๋์์ ๋ฟ์ด๋ค.
@Slf4j
@RestController // @ResponseBody๊ฐ ๋ถ์. ๋ทฐ๋ฆฌ์กธ๋ฒ ๋์ Str, Json์ผ๋ก ๋ณํ
@RequestMapping("/validation/api/items")
public class ValidationItemApiController {
@PostMapping("/add")
public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
log.info("๊ฒ์ฆ ์ค๋ฅ ๋ฐ์ errors={}", bindingResult);
return bindingResult.getAllErrors();
}
//...์ฑ๊ณต ๋ก์ง ์คํ...
return form; // MappingJacksonHttpMessageConverter์ ์ํด Object -> Json์ผ๋ก ๋ณํ๋จ.
}
}
ํ์ง๋ง ์ฌ๊ธฐ์๋ ํฐ ๋ฌธ์ ๊ฐ ์๋ค. Json์ ์ฃผ๊ณ ๋ฐ๋ค๊ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ, ์ปจํธ๋กค๋ฌ๊ฐ ํธ์ถ๋์ง ์๊ณ ์์ธ๊ฐ ํธ์ถ๋๋ค.์ฆ ์์ form ๊ฐ์ฒด๊ฐ ๋ง๋ค์ด์ง์ง ์๋๋ค → ๊ฒ์ฆ ๋ถ๊ฐ๋ฅ!
- @ModelAttribute ์์๋ ํ์ ์ด ๋ฌ๋ผ์ ๋ณํ์ด ์๋๋ ํ๋ ๋จ์(FieldError)๋ก ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
- ํ์ง๋ง @RequestBody๋ ๊ฐ๊ฐ์ ํ๋๋จ์๋ก ์ ์ฉํ ์ ์๋ค. ์ ์ฒด๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํด์ผ ํ๋ค.
์ฆ ์ฐ๋ฆฌ๋ API๋ฅผ ๋ง๋ค ๋ ์๋ 3๊ฐ์ง ์ํฉ์ ๋ํ ์์ธ์ฒ๋ฆฌ์ ์๋ง์ ์๋ต์ ํด์ฃผ์ด์ผ ํ๋ค. ์คํ๋ง์ ์์ธ์ฒ๋ฆฌ๋ ๋์ค์ ๋ฐ๋ก ๋ค๋ฃฐ์์ ์ด๋, ๊ทธ๋ ์์๋ณด๋๋ก ํ์.
- ์ฑ๊ณต ์์ฒญ : ์ฑ๊ณต ์ ๋ก์ง
- ์คํจ ์์ฒญ : Json -> ๊ฐ์ฒด ๋ณํ ์์ฒด๊ฐ ์คํจํจ ( form ๊ฐ์ฒด ์์ฑ ์คํจ, Vaildator ์ฌ์ฉ ๋ถ๊ฐ )
- ๊ฒ์ฆ ์ค๋ฅ ์์ฒญ : Json -> ๊ฐ์ฒด ๋ณํ์ ์ฑ๊ณตํ์ผ๋ ๊ฒ์ฆ์ ์คํจํจ.
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Login#2 ํํฐ, ์ธํฐ์ ํฐ, ArugmentResolver (0) | 2021.08.30 |
---|---|
Spring Login#1 ์ฟ ํค์ ์ธ์ (0) | 2021.08.29 |
Spring Vaildation#1 ๊ฒ์ฆ (0) | 2021.08.29 |
Spring ๋ฉ์์ง, ๊ตญ์ ํ ๊ธฐ๋ฅ (0) | 2021.08.28 |
Thymeleaf#2 Spring๊ณผ HTML Form ์ฒ๋ฆฌ (0) | 2021.08.28 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev