Curl Up Black Cat


๐Ÿ˜Ž ์„œ๋ก  

์•ˆ๋…•ํ•˜์„ธ์š” ์˜ค๋Š˜์€ Spring์—์„œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ๋ฌธ์ œ ์ค‘ ํ•˜๋‚˜์ธ API ์ž˜๋ชป๋œ ๋ฉ”์‹œ์ง€ ์š”์ฒญ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€ ์ž‘์„ฑํ•ด ๋ณผ๊ฒŒ์š”.

 

๊ฒŒ์‹œํŒ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๋ฉด์„œ ๋ฐฑ์—”๋“œ์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ API ์š”์ฒญ์„ ๋ฐ›๊ฒŒ ๋˜๋ฉด, ์š”์ฒญ์˜ JSON ํฌ๋งท์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘์—์„œ๋„ ์•Œ ์ˆ˜ ์—†๋Š” ํ•„๋“œ๋ฅผ ์ „๋‹ฌ ๋ฐ›์•˜์„ ๋•Œ ์ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ์„๊นŒ ๊ณ ๋ฏผ์„ ํ–ˆ๊ณ ..


๐Ÿค” HttpMessageNotReadableException ์ฒ˜๋ฆฌ

Spring ์—์„œ ์ œ๊ณตํ•˜๋Š” @ExceptionHandler ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HttpMessageNotReadableException ์„ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ, ๊ทธ ์›์ธ์ด ๋˜๋Š” cause ๋ฅผ ํ†ตํ•ด ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์ƒํ™ฉ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

@ExceptionHandler(value = {HttpMessageNotReadableException.class})
public ResponseEntity handleJsonParseException(HttpMessageNotReadableException ex) {
    final Throwable cause = ex.getCause();
    ...
}

 

์ด ๋•Œ, ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ด UnrecognizedPropertyException ์ด๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋Š” JSON์˜ ์•Œ ์ˆ˜ ์—†๋Š” ํ•„๋“œ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, ํ•ด๋‹น ํ•„๋“œ์˜ ์ด๋ฆ„์„ ์–ป์–ด์™€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.

if (cause instanceof UnrecognizedPropertyException) {
    final UnrecognizedPropertyException unrecognizedPropertyException = (UnrecognizedPropertyException) cause;
    final String fieldName = unrecognizedPropertyException.getPropertyName();
    ...
}

 

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ์–ป์–ด์˜จ ํ•„๋“œ ์ด๋ฆ„์„ ErrorCode.UNKNOWN_FIELD ์™€ ํ•จ๊ป˜ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ํ•ด๋‹น ํ•„๋“œ๊ฐ€ ๋ฌธ์ œ๋ผ๋Š” ๊ฒƒ์„ ์‘๋‹ตํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋ฉด, ํด๋ผ์ด์–ธํŠธ๋Š” ์–ด๋–ค ํ•„๋“œ ๋•Œ๋ฌธ์— ์š”์ฒญ์ด ์‹คํŒจํ–ˆ๋Š”์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.๐Ÿ˜€

 

์ „์ฒด ์†Œ์Šค๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด @ExceptionHandler ๋ฅผ ๊ตฌ์ถ•ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

@ExceptionHandler(value = {HttpMessageNotReadableException.class})
public ResponseEntity handleJsonParseException(HttpMessageNotReadableException ex) {
    final Throwable cause = ex.getCause();

    if (cause instanceof UnrecognizedPropertyException) {
        final UnrecognizedPropertyException unrecognizedPropertyException = (UnrecognizedPropertyException) cause;
        final String fieldName = unrecognizedPropertyException.getPropertyName();
        final String errorMessage = String.format(ErrorCode.UNKNOWN_FIELD.getMessage() + " : '%s'", fieldName);
        final ResponseModel responseModel = ResponseModel.failure(ErrorCode.UNKNOWN_FIELD, errorMessage);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseModel);
    }

    // ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
    final ResponseModel responseModel = ResponseModel.failure(ErrorCode.INVALID_JSON, ex.getMessage());
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseModel);
}

 

๊ทธ ๋ฐ–์— JSON ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ / ํƒ€์ž… ๋ถˆ์ผ์น˜ / JSON ๊ตฌ์กฐ์˜ ๋ถˆ์ผ์น˜ / ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ๋Š” ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ๋กœ ์‘๋‹ตํ•˜๋Š” INVALID_JSON ์œผ๋กœ ์‘๋‹ตํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


๐Ÿค  Response ์‘๋‹ต ๋ฉ”์‹œ์ง€

๊ตฌํ˜„๋œ ํ•ธ๋“ค๋Ÿฌ์˜ ์‘๋‹ต๋ฉ”์‹œ์ง€๋Š” ํ•œ๋ฒˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ™‚

 

์ž˜๋ชป๋œ ํ•„๋“œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์ด ๋˜์—ˆ์„ ๊ฒฝ์šฐ..

UnrecognizedPropertyException ์˜ˆ์™ธ

 

JSON ๋ฌธ๋ฒ•์˜ค๋ฅ˜ ๋“ฑ ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ์ด ๋˜์—ˆ์„ ๊ฒฝ์šฐ..

๊ทธ ๋ฐ–์˜ HttpMessageNotReadableException ์˜ˆ์™ธ


๐Ÿฅธ ๊ฒฐ๋ก 

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ๋‹จ์ˆœํžˆ ์˜ค๋ฅ˜๋ฅผ  ํฌ์ฐฉํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋Š”๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์™€์˜ ์›ํ™œํ•œ ์†Œํ†ต๊ณผ ํ˜‘์—…์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ด๋กœ ์ธํ•ด ๋ณด๋‹ค ๋‚˜์€ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฒŒ์‹œํŒ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๋ฉด์„œ ์ด์— ๋Œ€ํ•œ ์—ฐ๊ตฌ์™€ ๊ณ ๋ฏผ์„ ์ง€์†์ ์œผ๋กœ ํ•ด ๋‚˜๊ฐˆ ๊ณ„ํš์ž…๋‹ˆ๋‹ค.๐Ÿ™‚

 


๐Ÿ˜Ž ์„œ๋ก 

์•ˆ๋…•ํ•˜์„ธ์š”! ์˜ค๋Š˜์€ Spring Boot ์—์„œ API ์‘๋‹ต๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ชจ๋ธ์„ ๊ตฌ์ถ•ํ•ด๋ดค๋˜ ๋‚ด์šฉ์„ ๊ณต์œ ํ•ด ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๋“ค๋„ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ถ„์„ํ•ด ๋ณผ๊ป˜์š”. ์Šคํƒ€ํŠธ~!

 


1. ResponseModel - ์‘๋‹ต ๋ชจ๋ธ

๋จผ์ €, ๋ชจ๋“  API ์‘๋‹ต์— ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋  ResponseModel ์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค.

@Getter
@Setter
@ToString
@AllArgsConstructor(staticName = "of")
public class ResponseModel<T> {
    private boolean success;
    private T data;
    private ErrorModel error;

    public static <T> ResponseModel<T> of(boolean success, T data) {
        return ResponseModel.of(success, data, null);
    }

    public static ResponseModel of(boolean success, ErrorCode code) {
        return ResponseModel.of(success, null, ErrorModel.of(code.name(), code.getMessage(), null));
    }

    public static ResponseModel of(boolean success, ErrorCode code, Exception ex) {
        return ResponseModel.of(success, null, code, ex);
    }

    public static <T> ResponseModel<T> of(boolean success, T data, ErrorCode code, Exception ex) {
        final ErrorModel error = (code != null) ? ErrorModel.of(code, ex) : null;
        return ResponseModel.of(success, data, error);
    }
}

์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ:

1. success: ์š”์ฒญ ์ฒ˜๋ฆฌ์˜ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

2. data: ์š”์ฒญ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

3. error: ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ErrorModel ๊ฐ์ฒด ์ž…๋‹ˆ๋‹ค.

 

ResponseModel ์—๋Š” ์—ฌ๋Ÿฌ static ์ƒ์„ฑ ๋ฉ”์„œ๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋“ค์„ ํ†ตํ•ด ์„ฑ๊ณต ๋˜๋Š” ์˜ค๋ฅ˜ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์‰ฝ๊ฒŒ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

2. ErrorModel - ์—๋Ÿฌ ๋ชจ๋ธ

ErrorModel ์€ ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํ‘œ์ค€ํ™”ํ•˜์—ฌ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค.

 

package com.board.backend.model;

import com.board.backend.common.ErrorCode;
import com.board.backend.common.utils.ObjectUtil;
import com.board.backend.common.utils.StringUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@AllArgsConstructor(staticName = "of")
public class ErrorModel {
    private String code;
    private String message;
    private ErrorData data;

    @Getter
    @Setter
    @ToString
    @AllArgsConstructor(staticName = "of")
    public static class ErrorData {
        private String exceptionMessage;
        private String stackTrace;
    }

    public static ErrorModel of(ErrorCode code, Exception ex) {
        final String exceptionMessage = ObjectUtil.nonEmpty(ex) ? ex.getMessage() : null;
        final String stackTrace = ObjectUtil.nonEmpty(ex) ? getStackTraceAsString(ex) : null;
        final ErrorData data = (StringUtil.nonEmpty(exceptionMessage) || StringUtil.nonEmpty(stackTrace)) ? ErrorData.of(exceptionMessage, stackTrace) : null;
        return ErrorModel.of(code.name(), code.getMessage(), data);
    }

    private static String getStackTraceAsString(Exception ex) {
        final StringBuilder sb = new StringBuilder();
        for (StackTraceElement element : ex.getStackTrace()) {
            sb.append(element.toString());
            sb.append("\n");
        }
        return sb.toString();
    }
}

์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ:

1. code: ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

2. message: ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

3. data: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ์˜ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ErrorData ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

 

ErrorData ํด๋ž˜์Šค๋Š” ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ์˜ ๋ฉ”์‹œ์ง€์™€ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์˜ค๋ฅ˜์˜ ์›์ธ์„ ๋”์šฑ ์ž์„ธํžˆ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ž€? ํ”„๋กœ๊ทธ๋žจ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๋‹น ์˜ˆ์™ธ์˜ ๋ฐœ์ƒ ๊ฒฝ๋กœ๋ฅผ ์ถ”์ ํ•˜๋Š” ์ •๋ณด์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ˆœ์„œ์™€ ๋ผ์ธ ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์œ„์น˜์™€ ๊ทธ ์›์ธ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค.)

 

3. ErrorCode - ์—๋Ÿฌ ์ฝ”๋“œ

@Getter
@AllArgsConstructor
public enum ErrorCode {
    SERVER_ERROR("์„œ๋ฒ„ ์—๋Ÿฌ"),
    ARGUMENT_TYPE_MISMATCH("์ž˜๋ชป๋œ ํŒŒ๋ผ๋ฏธํ„ฐํƒ€์ž…์ด ์ „๋‹ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."),
    BOARD_NOT_FOUND("๊ธ€์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค");
    ...
    private String message;
}

๊ฐ ์˜ค๋ฅ˜ ์œ ํ˜•์— ๋Œ€ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ํ•จ๊ป˜ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜๋ฏธ ์žˆ๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ ํ•ฉ๋‹ˆ๋‹ค. enum ํƒ€์ž…์œผ๋กœ ์ฝ”๋“œ์™€ ๋ฉ”์‹œ์ง€๋ฅผ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

4. ExceptionAdvice - ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ

Spring Boot ์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์ด๋ฅผ ์œ„ํ•ด @ControllerAdvice ์™€ @ExceptionHandler ๋ฅผ ํ†ตํ•ด ํšจ๊ณผ์ ์ธ ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity handleException(Exception ex, WebRequest request) {
        final ResponseModel responseModel = ResponseModel.of(false, ErrorCode.SERVER_ERROR, ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(responseModel);
    }

    @ExceptionHandler(value = {MethodArgumentTypeMismatchException.class})
    public ResponseEntity handleTypeException(Exception ex) {
        final ResponseModel<Object> responseModel = ResponseModel.of(false, ErrorCode.ARGUMENT_TYPE_MISMATCH,  ex);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseModel);
    }
    ...
}

 

1) @ControllerAdvice ์˜ ์—ญํ• 

@ControllerAdvice ๋Š” Spring ์—์„œ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ๋“ค์„ ํ•œ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋„์ž…๋œ ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์ค‘๋ณต์„ ์ค„์ด๊ณ , ์ผ๊ด€๋œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ๋„์™€์ค๋‹ˆ๋‹ค. 

 

2) @ExceptionHandler 

@ExceptionHandler ๋Š” ํŠน์ • ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•œ Spring MVC ์˜ ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. ๊ทธ ์ž์ฒด๋กœ๋„ ์œ ์šฉํ•˜์ง€๋งŒ, @ControllerAdvice์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์˜ ์˜ˆ์™ธ๋ฅผ ์ค‘์•™์—์„œ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์˜ˆ์‹œ:

// ๋‹จ์ผ ์ง€์ •
@ExceptionHandler({CustomException.class})

// ๋‹ค์ค‘ ์ง€์ •
@ExceptionHandler({CustomException1.class, CustomException2.class, CustomException3.class})

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด, ์œ„์—์„œ ์ง€์ •๋œ ์˜ˆ์™ธ๋“ค์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

 

3) ํ™•์žฅ์„ฑ

@ControllerAdvice ์˜ ์žฅ์ ์ค‘ ํ•˜๋‚˜๋Š” ๊ทธ ํ™•์žฅ์„ฑ์ž…๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์˜ˆ์™ธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์„ ์ˆ˜์ •ํ•ด์•ผ ํ•  ๋•Œ, @ExceptionHandler ๋ฅผ ์ ์ ˆํ•œ ๋ฉ”์„œ๋“œ์— ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•จ์œผ๋กœ์จ ์ค‘์•™์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿค  ๊ฒฐ๋ก 

Spring Boot ํ™˜๊ฒฝ์—์„œ ๋‚˜๋ฆ„๋Œ€๋กœ ์ƒ๊ฐํ•ด์„œ ๊ตฌํ˜„ํ–ˆ๋˜ ์‘๋‹ต๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ผ๊ด€๋œ ์‘๋‹ต๊ณผ ์ •ํ™•ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” API์˜ ์‹ ๋ขฐ์„ฑ์„ ๋†’์ด๋Š” ํ•ต์‹ฌ ์š”์†Œ๋กœ, ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์™€์˜ ์‹ ๋ขฐ ๊ด€๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ , ๊ฐœ๋ฐœํŒ€ ๊ฐ„์˜ ํ˜‘์—… ํšจ์œจ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ™‚


๐Ÿ˜Ž ์„œ๋ก 

์•ˆ๋…•ํ•˜์„ธ์š”! ์˜ค๋Š˜์€ ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ JSON ๋ฐ์ดํ„ฐ๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ๋ ค๋“œ๋ฆฌ๋ ค ํ•ฉ๋‹ˆ๋‹ค. ์›น ์„œ๋น„์Šค์—์„œ ์ž‘์€ ์ตœ์ ํ™”๋„ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ํฐ ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ์—, ์ด๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋งŽ์€ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 


๐Ÿค” ์™œ null ๊ฐ’์„ ์ œ๊ฑฐํ•ด์•ผ ํ•˜๋‚˜์š”?

JSON ์‘๋‹ต ๋ฐ์ดํ„ฐ์—์„œ null ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ์ ์„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐํฌ๊ธฐ: ๋ถˆํ•„์š”ํ•œ null ๊ฐ’์€ ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ํฌ๊ธฐ๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ: ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” null ๊ฐ’์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

Before

{
    "id": 14,
    "title": "์ œ๋ชฉ",
    "content": "๋‚ด์šฉ",
    "writer": "ํ™๊ธธ๋™",
    "created_id": null
}

๐Ÿฅธ Jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ Null ๊ฐ’ ์ œ๊ฑฐํ•˜๊ธฐ

Spring Boot์—์„œ๋Š” Jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ JSON ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋ฉด, null ๊ฐ’์„ ๊ฐ€์ง„ ํ•„๋“œ๋Š” JSON ์‘๋‹ต์—์„œ ์ž๋™์œผ๋กœ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค.

 

package com.board.backend.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ObjectMapper objectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }
}

 

์ ์šฉ ํ›„์˜ ๊ฒฐ๊ณผ ์ž…๋‹ˆ๋‹ค.

 

After

{
    "id": 14,
    "title": "์ œ๋ชฉ",
    "content": "๋‚ด์šฉ",
    "writer": "ํ™๊ธธ๋™"
}

๐Ÿค  ๋งˆ์น˜๋ฉฐ

JSON ๋‚ด์˜ null ๊ฐ’์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•œ ์ ˆ์ฐจ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๋Ÿฌํ•œ ์ž‘์€ ์ตœ์ ํ™”๊ฐ€ ์„œ๋น„์Šค์˜ ์‘๋‹ต ์‹œ๊ฐ„ ๊ฐœ์„ ๊ณผ ํด๋ผ์ด์–ธํŠธ์˜ ์ฒ˜๋ฆฌ ๋ถ€๋‹ด ๊ฐ์†Œ์— ํฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ํŠนํžˆ, ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ์„œ๋น„์Šค๋‚˜ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ œํ•œ๋œ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด๋Ÿฐ ์ตœ์ ํ™”๊ฐ€ ๋”์šฑ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

๋ฐ์ดํ„ฐ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ์„ ํšจ์œจ์ ์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ์›น ์„œ๋น„์Šค์˜ ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์„ ํ†ตํ•ด ์†Œ๊ฐœ๋œ ๋ฐฉ๋ฒ•์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ์„œ๋น„์Šค์— ์ ์šฉ๋˜์–ด, ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•œ ๋‹จ๊ณ„ ๋” ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 


๐Ÿ˜Ž ์„œ๋ก 

์•ˆ๋…•ํ•˜์„ธ์š”, ์ธ๋”์ œ์ด ์ž…๋‹ˆ๋‹ค.

 

์˜ค๋Š˜์€ MyBatis XML Mapper์—์„œ Java์˜ ๋‚ด๋ถ€ ํด๋ž˜์Šค ์ฐธ์กฐ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์™€ ๊ทธ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค๋“œ๋ฆฌ๋ ค ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ ์ด ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ๋‹นํ•œ ์‹œ๊ฐ„์„ ํ—ค๋งธ๊ธฐ์— ์ด ๊ฒฝํ—˜์„ ์—ฌ๋Ÿฌ๋ถ„๊ณผ ๊ณต์œ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

 

๐Ÿฅฒ ๋ฌธ์ œ ์ƒํ™ฉ

๋จผ์ €, ์•„๋ž˜๋Š” Java ๋‚ด๋ถ€ ํด๋ž˜์Šค๋ฅผ ํฌํ•จํ•œ BoardDto ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค.

package com.board.backend.model;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BoardDto {

    @Getter
    @Setter
    public static class Response {
        private long id;
        private String title;
        private String content;
        private String writer;
        private long view_count;
        private Long created_id;
        private LocalDateTime created_dt;
        private LocalDateTime modified_dt;
    }
}

์ด ๋ชจ๋ธ์„ MyBatis XML Mapper์— resultType์œผ๋กœ ์„ค์ •ํ•˜๋ ค๊ณ  ํ–ˆ์„ ๋•Œ, ๋‚ด๋ถ€ ํด๋ž˜์Šค Response๋ฅผ ์ฐธ์กฐํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<select id="findById" resultType="com.board.backend.model.BoardDto.Response">
    SELECT *
    FROM board
    WHERE id = #{id}
</select>

ํ•˜์ง€๋งŒ ์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ–ˆ์„ ๋•Œ, ์—ฐ๊ฒฐ์ด ์ œ๋Œ€๋กœ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

Error creating bean with name 'boardMapper' defined in file  ...
Cannot resolve reference to bean 'sqlSessionTemplate' while setting bean property 'sqlSessionTemplate'

๐Ÿค” ์›์ธ 

sqlSessionTemplate ์—ฐ๊ฒฐ ์—๋Ÿฌ์˜ ์›์ธ์€ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค: ์„ค์ • ํŒŒ์ผ ๋ˆ„๋ฝ, ์„ค์ • ํŒŒ์ผ ์œ„์น˜ ๋ถˆ์ผ์น˜, ์˜์กด์„ฑ ๋ฌธ์ œ, ๋ฐ์ดํ„ฐ ์†Œ์Šค ์—ฐ๊ฒฐ ๋ฌธ์ œ, ๋นˆ ์ด๋ฆ„ ๋ถˆ์ผ์น˜, ์ƒ์„ฑ์ž/์„ธํ„ฐ ์ฃผ์ž… ๋ฌธ์ œ ๋“ฑ. ์ด ๋ชจ๋“  ๊ฐ€๋Šฅ์„ฑ์„ ํ™•์ธํ–ˆ์ง€๋งŒ, ๋ฌธ์ œ์˜ ์›์ธ์„ ์ฐพ์ง€ ๋ชปํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋˜ ์ค‘, Java์—์„œ ๋‚ด๋ถ€ ํด๋ž˜์Šค๋ฅผ ์ฐธ์กฐํ•  ๋•Œ $ ๊ธฐํ˜ธ๋กœ ์—ฐ๊ฒฐ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ™‚ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

Mapper XML์—์„œ resultType์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„  $ ๊ธฐํ˜ธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<select id="findById" resultType="com.board.backend.model.BoardDto$Response">
    SELECT *
    FROM board
    WHERE id = #{id}
</select>

ํ•ต์‹ฌ์€ BoardDto.Response ๋Œ€์‹  BoardDto$Response๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

๊ฒฐ๋ก ์ ์œผ๋กœ, MyBatis์—์„œ Java์˜ ๋‚ด๋ถ€ ํด๋ž˜์Šค๋ฅผ ์ฐธ์กฐํ•  ๋•Œ๋Š” $ ๊ธฐํ˜ธ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ด ์ž‘์€ ๋ถ€๋ถ„ ๋•Œ๋ฌธ์— ๋งŽ์€ ์‹œ๊ฐ„์„ ๋‚ญ๋น„ํ•˜์˜€๋Š”๋ฐ ์ด ๊ธ€์„ ํ†ตํ•ด ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๊ฒช๋Š” ๋ถ„๋“ค์—๊ฒŒ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๐Ÿ˜Œ

+ Recent posts