사용자가 서비스를 이용하면서 발생하는 예외가 발생되면 500번 코드만 보내줘서 클라이언트에서는 어떤 사항 때문에 에러가 발생한 지 확인 어려움이 있다고 생각합니다
직접 예외 코드와 메시지를 정의하여 정확한 상황 파악을 할 수 있도록 응답하는 시스템을 구현하기 위해서 @ControllerAdvice를 통해서 전역에서 예외를 처리할 수 있도록 구조를 설계하고 각 예외에 맞는 에러 메시지를 클라이언트에게 응답할 수 있도록 구현했습니다
예외처리 방법 구현에 대해서 설명합니다
1. ErrorCode 열거형 (enum) 정의
ErrorCode
package com.domain.openboard.error;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public enum ErrorCode {
VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "VALIDATION_ERROR", "잘못된 요청입니다."),
POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST_NOT_FOUND", "게시글을 찾을 수 없습니다."),
PASSWORD_MISMATCH(HttpStatus.UNAUTHORIZED, "PASSWORD_MISMATCH", "비밀번호가 일치하지 않습니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "서버 내부 오류가 발생했습니다.");
// 에러의 HTTP 상태 코드
private final HttpStatus httpStatus;
// 에러 코드
private final String code;
// 에러 메시지
private final String message;
}
모든 예외에 공통적으로 적용할 코드,메시지,상태코드를 관리하도록 구현했습니다
2. 공통 예외 응답 객체 정의
ErrorResponse
package com.domain.openboard.error;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
// 에러 메시지 응답용 객체
public class ErrorResponse {
private String code;
private String message;
}
예외가 발생 했을때 클라이언트에게 공통적으로 반환할 DTO입니다
"code" "message"만 포함하여 응답하도록 구현했습니다
3. 커스텀 예외 클래스 정의
CustomException
package com.domain.openboard.error;
import lombok.Getter;
@Getter
// RuntimeException을 상속한 사용자 정의 예외 클래스
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
public CustomException(ErrorCode errorCode) {
super(errorCode.getMessage()); // 부모 클래스에 전달
this.errorCode = errorCode;
}
}
RuntimeException을 상속한 사용자 정의 예외 클래스입니다
try-catch 없이도 예외를 발생시킬 수 있고 해당 클래스를 상속받아 각각의 예외 클래스를 구현하도록 했습니다
처음에 정의했던 ErrorCode를 필드로 가지고 있습니다
4. 전역 예외 처리기 클래스 구현
GlobalExceptionHandler
package com.domain.openboard.error;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
// 전역 예외 처리 클래스
// 모든 컨트롤러에서 발생하는 예외를 한 곳에서 처리
// 유효성 검사 실패,커스텀 예외,일반 예외를 구분하여 응답
public class GlobalExceptionHandler {
// DTO 유효성 검사 실패 예외 처리
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
ErrorCode errorCode = ErrorCode.VALIDATION_ERROR;
String message = ex.getBindingResult()
.getFieldErrors()
.stream()
.findFirst()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.orElse(errorCode.getMessage());
return ResponseEntity.badRequest()
.body(new ErrorResponse(errorCode.getCode(),message));
}
// 커스텀 예외 처리
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustom(CustomException ex) {
ErrorCode errorCode = ex.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus())
.body(new ErrorResponse(
errorCode.getCode(),
errorCode.getMessage()));
}
// 그외 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR;
return ResponseEntity.status(errorCode.getHttpStatus())
.body(new ErrorResponse(errorCode.getCode(), errorCode.getMessage()));
}
}
모든 컨트롤러에서 발생되는 예외를 한 곳에서 처리해 주는 클래스입니다
@RestControllerAdivce를 활용해서 구현했습니다
@RestControllerAdvice
모든 컨트롤러에서 발생되는 예외를 가로채서 처리하며 응답을 JSON 형태로 반환합니다
유효성 검사 실패 예외 처리
// DTO 유효성 검사 실패 예외 처리
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
ErrorCode errorCode = ErrorCode.VALIDATION_ERROR;
String message = ex.getBindingResult()
.getFieldErrors()
.stream()
.findFirst()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.orElse(errorCode.getMessage());
return ResponseEntity.badRequest()
.body(new ErrorResponse(errorCode.getCode(),message));
}
validation으로 유효성 검사를 구현했던 DTO에 대한 예외 처리를 담당합니다
@valid 어노테이션으로 검증이 실패했을 때 실행됩니다
에러 메시지 중 첫 번째 필드 에러 메시지를 가져와 사용 없는 경우 ErrorCode의 "VALIDATION_ERROR"메시지 사용
커스텀 예외 처리
// 커스텀 예외 처리
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustom(CustomException ex) {
ErrorCode errorCode = ex.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus())
.body(new ErrorResponse(
errorCode.getCode(),
errorCode.getMessage()));
}
사용자 정의 예외에 대한 처리를 담당합니다
비밀번호 불일치,게시글을 찾을 수 없음의 예외를 처리하도록 구현했습니다
PostNotFoundException
package com.domain.openboard.error.exception;
import com.domain.openboard.error.CustomException;
import com.domain.openboard.error.ErrorCode;
public class PostNotFoundException extends CustomException {
public PostNotFoundException() {
super(ErrorCode.POST_NOT_FOUND);
}
}
PasswordMismatchException
package com.domain.openboard.error.exception;
import com.domain.openboard.error.CustomException;
import com.domain.openboard.error.ErrorCode;
public class PasswordMismatchException extends CustomException {
public PasswordMismatchException() {
super(ErrorCode.PASSWORD_MISMATCH);
}
}
CustomException을 상속받는 각각의 예외 처리 클래스를 정의하여 사용합니다
이런 커스텀 예외가 발생되면 해당 메서드에서 처리하여 응답하게 됩니다
커스텀 예외 적용
PostService
// 게시글 삭제
public void delete(Long id,String inputPassword){
Post post = postRepository.findById(id).orElseThrow(PostNotFoundException::new);
// 입력 받은 password와 게시글에 저장된 password를 비교
if(!passwordEncoder.matches(inputPassword,post.getPassword())){
throw new PasswordMismatchException();
}
postRepository.delete(post);
}
// 게시글 수정
public Post update(Long id, PostUpdateRequestDto dto){
Post post = postRepository.findById(id).orElseThrow(PostNotFoundException::new);
if(!passwordEncoder.matches(dto.getPassword(),post.getPassword())){
throw new PasswordMismatchException();
}
post.update(dto.getTitle(),dto.getContent());
return post;
}
서버에서만 발생되는 에러 코드를 커스텀 예외를 적용하여 클라이언트에 각 상황에 맞는 응답하는 구조로 변경하였습니다
알 수 없는 예외 처리
// 그외 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR;
return ResponseEntity.status(errorCode.getHttpStatus())
.body(new ErrorResponse(errorCode.getCode(), errorCode.getMessage()));
}
위에서 정의하지 않은 예외는 이곳에서 처리하도록 했습니다
내부 서버 오류 메시지를 응답합니다
응답 형식
{
"code": "POST_NOT_FOUND",
"message": "게시글을 찾을 수 없습니다."
}
테스트
게시글 작성





게시글 단건 조회

게시글 삭제

게시글 수정


다음으로 유닛 테스트 구현 방법에 대해서 작성하겠습니다
'구름톤 유니브 스터디' 카테고리의 다른 글
| [구름톤 유니브] 9oormthonUNIV 4기를 마무리 하며 (0) | 2025.10.16 |
|---|---|
| [구름톤 유니브] Spring 커뮤니티 게시판 JUnit 프레임워크를 활용하여 테스트 코드 작성하기 (0) | 2025.03.31 |
| [구름톤 유니브] Spring 커뮤니티 게시판 validation을 이용한 유효성 검사를 구현 (0) | 2025.03.30 |
| [구름톤 유니브] Spring 커뮤니티 게시판 CRUD 만들기 2 (조회,수정,삭제 기능 제작) (0) | 2025.03.30 |
| [구름톤 유니브] Spring 커뮤니티 게시판 CRUD 만들기 1 (엔티티 생성,게시글 작성 기능) (0) | 2025.03.29 |