Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[20210722] IntelliJ API 테스트, @Valid(유효성 검사), 예외 핸들링 #183

Open
JuHyun419 opened this issue Jul 22, 2021 · 0 comments

Comments

@JuHyun419
Copy link
Owner

generated-requests.http (IntelliJ에서 API 테스트), 포스트맨과 비슷

  • 컨트롤러에서 아래와 같은 나뭇잎(?) 클릭하면 해당 API의 URI가 생성
  • 서버가 실행된 상태에서 테스트가 가능(당연히 !)

image

image



@Valid => 유효성 검사(@notblank) 에러 출력 & 반환

Error 핸들링이 없는경우

// build.gradle
...
implementation("org.springframework.boot:spring-boot-starter-validation")


import javax.validation.constraints.NotBlank;

// import & annotation
public class NoticeRequestDto {

    @NotBlank(message = "제목은 필수 항목입니다.")
    private String title;

    @NotBlank(message = "내용은 필수 항목입니다.")
    private String contents;

}


// import
@RestController
@Log4j2
public class SampleController {

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(@RequestBody @Valid NoticeRequestDto dto) {
        Notice notice = Notice.builder()
                .title(dto.getTitle())
                .contents(dto.getContents())
                .hits(0)
                .likes(0)
                .regDate(LocalDateTime.now())
                .build();
        log.info("notice: {}", notice);

        return ResponseEntity.ok().build();
    }
}

image

  • 예상한 결과: Dto에 설정한 @notblank의 message가 출력
  • 실제 결과: 400 Bad Request를 리턴

Error 핸들링 처리하기

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.validation.FieldError;

@Data
@Builder
@AllArgsConstructor
public class ResponseError {
    private String field;
    private String message;

    private ResponseError() {
    }

    public static ResponseError of(FieldError e) {
        return ResponseError.builder()
                .field(e.getField())
                .message(e.getDefaultMessage())
                .build();
    }
}


...

import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;

@RestController
@Log4j2
public class SampleController {

    @PostMapping("/api/notice")
    public ResponseEntity<Object> addNotice(
            @RequestBody @Valid NoticeRequestDto dto,
            Errors errors) {

        if (errors.hasErrors()) {
            List<ResponseError> responseErrors = new ArrayList<>();
            errors.getAllErrors().forEach(error ->
                    responseErrors.add(ResponseError.of((FieldError) error)));

            return new ResponseEntity<>(responseErrors, HttpStatus.BAD_REQUEST);
        }

        Notice notice = Notice.builder()
                .title(dto.getTitle())
                .contents(dto.getContents())
                .hits(0)
                .likes(0)
                .regDate(LocalDateTime.now())
                .build();
        log.info("notice: {}", notice);

        return ResponseEntity.ok().build();
    }

image

  • @notblank 의 Message가 정상적으로 리턴됨

@ControllerAdvice를 통한 글로벌 예외 처리

image

// 추 후 @ControllerAdvice를 통한 글로벌 예외 처리

package com.github.oneline.onelinecourse.common.error;

import com.github.oneline.onelinecourse.common.Messages;
import com.github.oneline.onelinecourse.common.error.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;

import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    private ResponseEntity<ErrorResponse> newResponse(final ErrorResponse response, final HttpStatus status) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        return new ResponseEntity<>(response, headers, status);
    }

    @ExceptionHandler(value = {Exception.class, RuntimeException.class})
    public ResponseEntity<ErrorResponse> handleUnexpectedException(RuntimeException e) {
        log.error("handleUnexpectedException", e);

        final ErrorResponse response = ErrorResponse.of(Messages.UNEXPECTED_EXCEPTION_MESSAGE);
        return newResponse(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.error("handleBusinessException", e);

        final ErrorResponse response = ErrorResponse.of(e.getMessage());
        return newResponse(response, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(value = {
            IllegalStateException.class, IllegalArgumentException.class,
            TypeMismatchException.class, HttpMessageNotReadableException.class,
            MultipartException.class, MissingServletRequestParameterException.class
    })
    public ResponseEntity<ErrorResponse> handleBadRequestException(Exception e) {
        log.debug("handleBadRequestException", e);

        final ErrorResponse response = ErrorResponse.of(e.getMessage());
        return newResponse(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * javax.validation.Valid & @Valid 예외 처리
     * HttpMessageConverter 바인딩 관련 발생
     * -> @RequestBody, @RequestPart 어노테이션에서 주로 발생
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("handleMethodArgumentNotValidException", e);

        final String message = e.getBindingResult().getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(" "));
        log.error(message);

        final ErrorResponse response = ErrorResponse.of(message);
        return newResponse(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * hibernate entity manager 관련된 제악 샤항 에러 처리
     * entity 에서 사용하고 있는 filed 관련 에러
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorResponse> handleEntityValidationException(ConstraintViolationException e) {
        log.error(e.getMessage());

        final ErrorResponse response = ErrorResponse.of(e.getMessage());
        return newResponse(response, HttpStatus.BAD_REQUEST);
    }

}
@JuHyun419 JuHyun419 changed the title [20210722] IntelliJ API 테스트, @Valid(유효성 검사) - Error Handling [20210722] IntelliJ API 테스트, @Valid(유효성 검사), 예외 핸들링 Jul 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant