Memo API (3-2)

Create Memo

이번 포스트에서는 메모를 생성할 때 필요한 데이터의 유효성 검사예외 처리에 대한 기능을 구현했다.

유효성 검사

build.gradle

유효성 검사와 관련된 Annotation을 사용하기 위해 build.gradle에 아래의 의존성을 추가한다.

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

MemoController

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun createMemo(
        @ModelAttribute @Valid createMemoRequest: CreateMemoRequest,
    ) {
        memoService.createMemo(createMemoRequest)
    }
data class CreateMemoRequest(
    @field:NotEmpty(message = "Title must not be empty.")
    @field:Size(max = 45, message = "Title must not exceed 45 characters.")
    val title: String,

    @field:NotEmpty(message = "Content must not be empty.")
    val content: String,

    @field:Size(max = 3, message = "Up to 3 tags can be registered.")
    val tags: List<String>,

    @field:Size(max = 3, message = "Up to 3 files can be registered.")
    val files: List<MultipartFile>
)

Controller에서 요청 데이터에 대한 유효성 검사를 하기 위해 CreateMemoRequest 앞에 @Valid를 추가했다.
@field:(???)를 통해 CreateMemoRequest의 멤버필드 각각에 대해 유효성 검사한다.

List에 @field:Size(max = 3, ~)를 설정했는데 이는 list 안에 있는 element 개수가 최대 3개라는 의미이다.
List 안에 있는 element에 대해서도 유효성 검사를 하고 싶었는데 찾지 못했다.

GeneralExceptionHandler

API에서 발생하는 예외는 모두 ExceptionHandler를 통해 처리한다.

@RestControllerAdvice
@Order(HIGHEST_PRECEDENCE)
class GeneralExceptionHandler {
    private val log = KotlinLogging.logger { }

    @ExceptionHandler(BindException::class, MethodArgumentNotValidException::class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    fun handleRequestValid(exception: BindException): ErrorResponse {
        val builder = StringBuilder()
        for (error in exception.bindingResult.fieldErrors)
            builder.appendLine("[field] ${error.field} [message] ${error.defaultMessage} [rejectedValue] ${error.rejectedValue}")
        log.info { builder.toString() }
        return ErrorResponse(HttpStatus.BAD_REQUEST, "System-003", builder.toString())
    }
}

@ModelAttribute로 받은 데이터가 유효하지 않으면 BindingException이 발생하고
@RequestBody로 받은 데이터가 유효하지 않으면 MethodArgumentNotValidException이 발생한다.

예외가 발생하면 Timestamp, HttpResponse, ErrorCode, ErrorMessage가 들어있는 ErrorResponse를 return한다.

Request

title은 빈 값, tags에 들어있는 tag의 개수는 5개로 만들어 요청하면
@ModelAttribute CreateMemoRequest의 멤버필드에 대해 validation이 실패하기 때문에 BindException이 발생한다.
BindingException이 발생하면 GeneralExceptionHandler가 해당 Exception에 대해 ErrorResponse를 return한다.

유효성 검사가 제대로 이루어진 것을 확인할 수 있다.

예외 처리

Service 로직에 대한 예외 처리는 Service 메서드에 @Transactional Annotation만 추가하면 된다.

@Transactional
    fun createMemo(createMemoRequest: CreateMemoRequest) { ... }
    @Transactional
    fun createTags(memo: Memo, tagsFromRequest: List<String>) { ... }
    @Transactional
    fun createFiles(memo: Memo, filesFromRequest: List<MultipartFile>?) { ... }

서비스 로직을 실행하는 도중에 예외가 발생하면 Transaction에 의해 기존의 쿼리문이 전부 rollback된다.

좋은 웹페이지 즐겨찾기