기존의 프로젝트에 Bean Validation을 적용해보았다.
<Item>
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class Item {
private Long id;
@NotBlank(message = "공백X")
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Range(max = 9999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
<ValidationItemController>
package hello.itemservice.web.validation;
import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Slf4j
@Controller
@RequestMapping("validation/v3/items")
@RequiredArgsConstructor
public class ValidationItemControllerV3 {
private final ItemRepository itemRepository;
@GetMapping
public String items(Model model) {
List<Item> items = itemRepository.findAll();
model.addAttribute("items", items);
return "validation/v3/items";
}
@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "validation/v3/item";
}
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "validation/v3/addForm";
}
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
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}";
}
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "validation/v3/editForm";
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
itemRepository.update(itemId, item);
return "redirect:/validation/v3/items/{itemId}";
}
}
addItem에 @Validated가 붙으면서 자동으로 검증대상에 추가가 되고 Item 필드값으로 잘못된 정보를 입력하면 다음과 같은 과정으로 검증이 실행된다.
1. @ModelAttribute 각각의 필드에 타입변환 시도
1) 성공하면 다음으로
2) 실패하면 typeMismatch로 FieldError 추가
2. Validator 적용
여기서 알아둬야 할 점은 바인딩에 성공한 필드만 Bean Validation이 적용된다.
만약 타입변환에 실패하면 typeMismatch FieldError 추가되고 Bean Validation에는 적용되지 않는다.
에러 코드 등록
NotBlank라는 오류 코드를 기반으로 errors.properties에 오류 메시지를 입력할 수 있다.
NotBlank.item.itemName=상품 이름을 적어주세요.
#Bean Validation 추가
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
<실행 결과>
오브젝트 오류 처리
오브젝트 오류를 처리하기 위해서는 @ScriptAssert()를 사용하면 된다.
<Item>
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ScriptAssert(lang="javascript", script = "_this.price * _this.quantity>=10000", message = "총합이 10000원 넘게 해주세요") //오브젝트 오류를 처리하기 위해 추가
public class Item {
private Long id;
@NotBlank(message = "공백X")
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Range(max = 9999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
<실행결과>
이 방법이 편리하긴 하지만, 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다고 한다.
따라서 오브젝트 오류는 자바 코드로 다루는것을 권장한다.
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
'BackEnd > spring' 카테고리의 다른 글
회원가입 로그인 구현 (0) | 2024.08.19 |
---|---|
BeanValidation-Form 전송 객체 분리 (0) | 2024.08.05 |
Validator 분리 (1) | 2024.07.31 |
검증 - 개선 (1) | 2024.07.26 |
검증 - BindingResult (0) | 2024.07.26 |