Validation - 3 (~ @Validated 까지)

나무·2023년 11월 17일

스프링 MVC

목록 보기
4/12
post-thumbnail

이전 장의 문제는 검증로직이 컨트롤러 내에서 너무 많은 부분을 차지 하는것이였다. 그러므로, 이번장에서는 검증로직을 컨트롤러로부터 분리를 시켜보겠다.

0. Validator 생성

컨트롤러와 같은 패키지에 ItemValidator 라는 객체를 하나 생성해준다. 그리고 그냥 순수 자바로 짜도 되지만 이번에는 스프링이 지원해주는 Validator 인터페이스를 구현한다.

1. V5 : Vaildator 도입

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);
    }

    @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);
            }
        }
    }
}

Controller

@Autowired
private final ItemValidator itemValidator;


@PostMapping("/add")
    public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

        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 를 만들 때 왜 굳이 spring의 Validator 인터페이스를 구현했을까?

그 이유는 바로 @Validated 어노테이션을 사용 하기 위해서 이다.

2 V6 : @Vaildated 도입

Controller

	@Autowired
	private final ItemValidator itemValidator;

	@InitBinder
	public void init(WebDataBinder dataBinder) {
		dataBinder.addValidators(itemValidator);
	}

	@PostMapping("/add")
    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}";
    }

@WebDateBinder

@Vaildated 를 사용하려면 우선 @WebDateBinder 검증기에 itemValidator 를 등록해줘야한다.

@InitBinder
	public void init(WebDataBinder dataBinder) {
		dataBinder.addValidators(itemValidator);
	}

하지만 @WebDateBinder 에 다른 여러 검증기를 등록할수 있다면 그것들 중 어떤 검증기가 실행되어야하는지 어떻게 알 수 있을까?

supports()

앞서 등록해줬던 ItemValidator 코드를 살펴보면 supports() 메서드가 오버라이딩 된것을 확인 할 수 있다.

바로 이 supports() 를 통해 어떤 검증기가 실행되어야할지 판단을 할 수 있는것이다.

+ @Vaild

@Valid 또한 사용 가능한데 이건 자바 표준 검증 어노테이션이다. 이걸 사용하려면

implementation 'org.springframework.boot:spring-boot-starter-validation'

build.gradle 에 추가해주면 된다.

본 포스트는
김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 를 보고 정리했습니다.

profile
🍀 개발을 통해 지속 가능한 미래를 만드는데 기여하고 싶습니다 🍀

0개의 댓글