FireDrago

[bobzip] 사용자정의 Validation으로 중복검사 하기 본문

프로젝트

[bobzip] 사용자정의 Validation으로 중복검사 하기

화이용 2024. 6. 11. 09:54

문제상황

1. Bean Validation의 기본 기능으로는 중복검사를 할 수 없다.

    - DB 데이터와 대조하는 작업이 필요하다.

    - 에너테이션으로는 DB 데이터 대조하는 기능이 없다. 직접 로직을 설정해줘야 한다. 

 

2. Thymeleaf 의 th:errors 를 사용하기 위해, 필드에러에 중복결과를 담고싶다.

 

해결방법

사용자 정의 Validation 객체를 만들고, 등록하여 @Validated 검증과정에 중복검사가 포함될 수 있도록 하자

 

사용자 정의 Validation 객체는 Validator 를 구현하여 만들어진다. Validator를 살펴보자

public interface Validator {

    boolean supports(Class<?> clazz);
    
    void validate(Object target, Errors errors);
}
 
 

supports (Class<?> clazz) : 검증로직이 적용될 클래스가 맞는지 확인한다.

validate(Object target, Errors errors) : 검증로직을 정의한다. 

 

@Component
@RequiredArgsConstructor
public class SignValidator implements Validator {

    private final MemberRepository memberRepository;

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.isAssignableFrom(SignForm.class);
    }

    @Override
    public void validate(Object target, Errors errors) {
        SignForm signForm = (SignForm) target;
        if (memberRepository.existsByUsername(signForm.getUserId())) {
            errors.rejectValue("userId", "duplicate.userId", "이미 사용중인 아이디입니다.");
        }

        if (memberRepository.existsByUsername(signForm.getUsername())) {
            errors.rejectValue("username", "duplicate.username", "이미 사용중인 닉네임입니다.");
        }
    }
}

 

 
 

@Component : 사용자정의 검증로직은 Validator 를 구현하고 빈으로 등록해야 한다.

 

clazz.isAssignableFrom(SignForm.class) : 입력 DTO SignForm 에 검증로직을 적용한다.

 

public interface IngredientRepository extends JpaRepository<Ingredient, Long> {
    boolean existsByName(String name);
}

memberRepository.existsByUsername(signForm.getUserId()) :

    리포지토리에 existsBy(필드명) 사용하여 중복확인 로직을 정의했다. 

 

errors.rejectValue("userId", "duplicate.userId", "이미 사용중인 아이디입니다.") :

    중복이 확인된 경우 Errors.rejectValue(필드명, 에러코드, 기본메시지) 를 통해 필드에러를 발생시킨다.

 

이제 컨트롤러에서 검증로직을 적용하는 방법을 알아보자

@Slf4j
@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final SignValidator signValidator;

    @InitBinder
    public void validatorBinder(WebDataBinder binder) {
        binder.addValidators(signValidator);
    }

    @GetMapping("/add")
    public String addForm(@ModelAttribute("member") SignForm member) {
        return "members/addForm";
    }

    @PostMapping("/add")
    public String add(@Validated @ModelAttribute("member") SignForm signForm,
                      BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "members/addForm";
        }

        Member member = new Member(signForm.getUserId(), signForm.getPassword(), signForm.getUsername());
        memberService.add(member);
        return "redirect:/";
    }
}
@InitBinder
public void validatorBinder(WebDataBinder binder) {
    binder.addValidators(signValidator);
}
 
 

@InitBinder : WebDataBinder 를 초기화하는 메서드에 붙인다.

binder.addValidators(signValidator) : 주입받은 SignValidator를 WebDataBinder에 추가한다.

WebDataBinder : 사용자가 입력한 값 (HTML Form, Query 데이터)를

스프링의 객체 (SignForm) 에 바인딩하는 역할을 한다.

이 과정에서 Validator, Formatter, Converter 등이 사용된다.

당연히 사용자 정의 Validator, Formatter, Converter 역시 여기서 등록한다.

 

1. 사용자가 입력한 HTML Form 값이 전달된다. 

2. WebDataBinder가 SignForm 객체에 값을 바인딩 하기위해 초기화 된다.

3. 에너테이션을 사용한 BeanValidation이 실행된다. 에러는 BindingResult에 등록(ex) @NotNull 등

4. SignValidatior 실행된다. 중복검사 한다. 중복된 경우 Errors.rejectValue 로 필드에러 등록한다.

타임리프에 필드에러 기본 메시지가 표시된다.