슬기로운 개발생활

[Spring Boot] Validation 적용, @Valid로 유효성 검사하기

by coco3o
반응형

이전에는 spring-boot-starter-web 의존성 내부에 validation이 있었지만,

spring boot 2.3 version 이상부터는 아예 모듈로 빠져 validation 의존성을 따로 추가해줘야 사용할 수 있다.

 


1. validation 의존성 추가

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

2. 유효성 검사 예시

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserRequestDto {

    @NotBlank(message = "아이디는 필수 입력 값입니다.")
    private String username;

    @NotBlank(message = "비밀번호는 필수 입력 값입니다.")
    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
    private String password;

    @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9-_]{2,10}$", message = "닉네임은 특수문자를 제외한 2~10자리여야 합니다.")
    private String nickname;

    @Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "이메일 형식이 올바르지 않습니다.")
    @NotBlank(message = "이메일은 필수 입력 값입니다.")
    private String email;

    ...
}

유효성 검사가 필요한 Request 객체에 Validation 어노테이션을 사용해 유효성 검사를 적용할 수 있다.


3. 유효성 검사 어노테이션 종류

어노테이션 설명
@Null null만 허용한다.
@NotNull 빈 문자열(""), 공백(" ")은 허용하되, Null은 허용하지 않음
@NotEmpty 공백(" ")은 허용하되, Null과 빈 문자열("")은 허용하지 않음
@NotBlank null, 빈 문자열(""), 공백(" ") 모두 허용하지 않는다.
@Email 이메일 형식을 검사한다. 단, 빈 문자열("")의 경우엔 통과 시킨다. ( @Pattern을 통한 정규식 검사를 더 많이 사용
@Pattern(regexp = ) 정규식 검사할 때 사용한다.
@Size(min=, max=) 길이를 제한할 때 사용한다.
@Max(value = ) value 이하의 값만 허용한다.
@Min(value = ) value 이상의 값만 허용한다.
@Positive 값을 양수로 제한한다.
@PositiveOrZero 값을 양수와 0만 가능하도록 제한한다.
@Negative 값을 음수로 제한한다.
@NegativeOrZero 값을 음수와 0만 가능하도록 제한한다.
@Future Now 보다 미래의 날짜, 시간이어야 한다.
@FutureOrPresent Now 거나 미래의 날짜, 시간이어야 한다.
@Past Now 보다 과거의 날짜, 시간이어야 한다.
@PastFutureOrPresent Now 거나 과거의 날짜, 시간이어야 한다.

4. Validation 적용해보기

    /* 회원가입 */
    @PostMapping("/auth/joinProc")
    public String joinProc(@Valid UserRequestDto userDto, Errors errors, Model model) {

        if (errors.hasErrors()) {
            /* 회원가입 실패시 입력 데이터 값을 유지 */
            model.addAttribute("userDto", userDto);

            /* 유효성 통과 못한 필드와 메시지를 핸들링 */
            Map<String, String> validatorResult = userService.validateHandling(errors);
            for (String key : validatorResult.keySet()) {
                model.addAttribute(key, validatorResult.get(key));
            }
            /* 회원가입 페이지로 다시 리턴 */
            return "/user/user-join"; 
        }
        userService.userJoin(userDto);
        return "redirect:/auth/login";
    }

컨트롤러에서 Request 객체 앞에 @Valid 어노테이션을 사용하고, Errors를 통해 유효성 검사 적합 여부를 확인한다.

※ 이때 Errors는 반드시 Request 객체 바로 뒤에 위치해야 한다.

(두 개의 객체에 validation 검사를 한다면, 각각 객체 뒤에 Errors를 받도록 한다.)

 

그리고 hasErrors() 메서드를 통해 Request 객체에 설정한 유효성 검사에 문제가 있는지 확인하고,

 

문제가 있다면 원하는 데이터 형식으로 가공해서 사용하면 된다.

 

Service

    /* 회원가입 시, 유효성 체크 */
    public Map<String, String> validateHandling(Errors errors) {
        Map<String, String> validatorResult = new HashMap<>();

        for (FieldError error : errors.getFieldErrors()) {
            String validKeyName = String.format("valid_%s", error.getField());
            validatorResult.put(validKeyName, error.getDefaultMessage());
        }
        return validatorResult;
    }

유효성 검사에 실패한 필드들은 Map 자료구조를 이용해 키값과 에러 메시지를 응답한다.

Key : valid_{dto 필드명}

Message : dto에서 작성한 message 값

 

유효성 검사에 실패한 필드 목록을 받아 미리 정의된 메시지를 가져와 Map에 넣어준다.

 

Mustache

{{>layout/header}}
<div id="posts_list">
    <div class="container col-md-8">
        <form action="/auth/joinProc" method="post" modelAttribute="userDto">
            <input type="hidden" name="_csrf" value="{{_csrf.token}}"/>
            <div class="form-group">
                <label>아이디</label>
                <input type="text" name="username" value="{{#userDto}}{{userDto.username}}{{/userDto}}" class="form-control" placeholder="아이디를 입력해주세요"/>
                {{#valid_username}} <span id="valid">{{valid_username}}</span> {{/valid_username}}
            </div>

            <div class="form-group">
                <label>비밀번호</label>
                <input type="password" name="password" value="{{#userDto}}{{userDto.password}}{{/userDto}}" class="form-control" placeholder="비밀번호를 입력해주세요"/>
                {{#valid_password}} <span id="valid">{{valid_password}}</span> {{/valid_password}}
            </div>

            <div class="form-group">
                <label>닉네임</label>
                <input type="text" name="nickname" value="{{#userDto}}{{userDto.nickname}}{{/userDto}}" class="form-control" placeholder="닉네임을 입력해주세요"/>
                {{#valid_nickname}} <span id="valid">{{valid_nickname}}</span> {{/valid_nickname}}
            </div>

            <div class="form-group">
                <label>이메일</label>
                <input type="email" name="email" value="{{#userDto}}{{userDto.email}}{{/userDto}}" class="form-control" placeholder="이메일을 입력해주세요"/>
                {{#valid_email}} <span id="valid">{{valid_email}}</span> {{/valid_email}}
            </div>

            <button class="btn btn-primary bi bi-person"> 가입</button>
            <a href="/" role="button" class="btn btn-info bi bi-arrow-return-left"> 목록</a>

        </form>
    </div>
</div>
{{>layout/footer}}


참고 자료 : 

https://wildeveloperetrain.tistory.com/25

https://victorydntmd.tistory.com/332?category=764331

반응형

블로그의 정보

슬기로운 개발생활

coco3o

활동하기