검증
타입, 필드 등 오류가 발생할 시 수행하는 로직을 뜻한다.
1. 검증 직접 처리
Map<String, String> errors = new HashMap<>();
을 사용하여 어떤 검증에서 오류가 발생했는지 정보를 담는다.
문제점:
- 만약 문자타입으로 입력이 들어오면 오류가 발생한다.
- 오류 발생시 입력 내용이 사라짐
2. BindingResult
BindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다.
필드에 오류가 발생하면 FieldError를 사용하고 글로벌 오류가 발생하면 ObjectError을 사용한다.
FieldError , ObjectError 생성자 소개
- objectName : @ModelAttribute 이름
- field : 오류가 발생한 field 이름
- defaultMessage: 오류 기본 메시지
문제점
- 오류가 발생할 시 데이터가 유지되지 않는 다는 단점이 있다.
3. BindingResult2
BindingResult에 대한 자세한 이야기를 다룬다.
BindingResult가 없으면 400 오류가 발생하여 컨트롤러가 호출되지 않고 오류 페이지로 이동한다
BindingResult가 있으면 오류 정보(FieldError)를 BindingResult에 담아서 컨트롤를 정상 호출한다.
4. FieldError, ObjectError
오류메세지가 화면에 남도록 하기
ex) 가격을 1000원 미만으로 설정할 시 그 값이 남아있어야 한다.
파라미터 목록
- objectName : 오류가 발생한 객체 이름
field : 오류 필드
rejectedValue : 사용자가 입력한 값(거절된 값)
bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
codes : 메시지 코드
arguments : 메시지에서 사용하는 인자
defaultMessage : 기본 오류 메시지
5. 오류코드와 메시지 처리
오류 메시지를 체계적으로 다룬다.
바로 위에서 언급했듯이 codes와 arguments들은 null로 처리되어 있는데 이 파라미터들을 사용한다.
5-1 오류코드와 메시지 처리1
errors 메시지 파일 생성한다.
다음과 같이 코드가 바뀐다.
codes에서 String 배열을 사용하는 이유는 만약 첫번째 배열에 있는 값이 없을 경우 두번째, 세번째 배열을 사용하려고 하는것이다.
ex) 만약 required.item.itemName, "default" 라고 배열이 되어있을 경우 required.item.itemName가 없을 시 "default" 가 실행된다.
5-2 오류코드와 메시지 처리2
FieldError와 ObjectError는 다루기가 너무 번거로워서 오류 코드를 좀 더 자동화 할 수 있는 것을 다루었다.
rejectValue(), reject()를 사용하면 FieldError , ObjectError 를직접 생성하지 않고, 깔끔하게 검증 오류를 다룰 수 있다.
참고로 BindingResult 는 어떤 객체를 대상으로 검증하는지 target을 이미 알고 있다고 했다. 따라서 target(item)에 대한 정보는 없어도 된다.
5-3 오류코드와 메시지 처리3
어떤식으로 오류코드를 설계하는 것인가에 대해 고민하는 것이 중요하다.
단순하게 만들면 범용성이 좋다. 하지만 메시지를 세밀하게 작성하기 어렵다.
반대는 너무 자세하게 만들면 범용성이 떨어진다.
따라서 범용성을 사용하다가 세밀한 내용을 적어야하는 경우 메시지이에 단계를 두는 방법이다.
세밀한 메시지를 높은 우선순위로 사용한다.
5-4 오류코드와 메시지 처리4
MessageCodesResolver에 대해 알아보았다
5-5 오류코드와 메시지 처리5
MessageCodesResolver는 구체적인 것을 먼저 만들어주고 덜 구체적인 것을 가장 나중에 만든다.
5-6 오류코드와 메시지 처리6
검증 오류 코드는 다음과 같이 2가지가 있다.
- 개발자가 직접 설정한 오류 코드 -> rejectValue()를 직접 호출
- 스프링이 직접 검증 오류에 추가한 경우(주로 타입 정보가 맞지 않음)
필드에 문자를 집어넣게 되면 typeMismatch가 뜨게 된다.
typeMismatch.item.price
typeMismatch.price
typeMismatch.java.lang.Integer
typeMismatch
그래서 error.properties에
이 내용을 추가하게 되면 소스코드를 하나도 건들지 않고 원하는 메시지가 나오게 된다.
6. Validator 분리
복잡한 검증 로직을 별도로 분리하는것이다.
6.1 Validator 분리1
ItemValidator를 만든다.
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);
//item == clazz
//item == subItem
}
@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, 1000000}, 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);
}
}
}
}
supports() {} : 해당 검증기를 지원하는 여부 확인(뒤에서 설명)
validate(Object target, Errors errors) : 검증 대상 객체와 BindingResult
v4에서의 검증부분을 지우고 itemValidator.validate(item, bindingResult);를 호출해주면 된다.
6.1 Validator 분리2
validator 인터페이스를 사용한다.
다음코드를 추가한다.
@InitBinder
public void init(WebDataBinder dataBinder){
dataBinder.addValidators(itemValidator);
}
@Validated는 검증기를 실행하라는 어노테이션이다.
하지만 여러 검증기를 등록한다면 구분을 할 필요가 있다.
이때 supports()가 사용된다.
'백엔드 > 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술' 카테고리의 다른 글
검증2 - Validation (0) | 2023.03.15 |
---|---|
메시지, 국제화 (0) | 2023.01.28 |
타임리프 - 기본 기능 (0) | 2023.01.23 |