[Backend/Spring] Spring Boot - 예외 처리

2026. 3. 29. 21:24·Backend/Spring

일단 지금 코드에서 존재하지 않는 회원을 조회하면 어떤 응답이 오는지 확인해보자.

없는 회원에 대한 정보를 요청했기 때문에 에러가 발생할 것은 당연히 예상할 수 있다.

그치만 여기에 2가지 문제가 있다.

  1. "status" 가 500(서버 오류)인데 지금은 서버 오류가 아니고 클라이언트가 잘못된 ID를 요청한 것이므로 404(클라이언트 오류)여야 한다.
  2. 에러 메시지가 없어서 클라이언트 입장에서 뭐가 문제인지 알 수 없다.

커스텀 예외 클래스 만들기

com.seongmo.myshop 하위에 exception 패키지를 만들자

com.seongmo.myshop
├── member
├── item
└── exception
    ├── BusinessException.java
    ├── ErrorCode.java
    ├── ErrorResponse.java
    └── GlobalExceptionHandler.java

ErrorCode.java 만들기

package com.seongmo.myshop.exception;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum ErrorCode {
    MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 회원입니다."),
    DUPLICATE_EMAIL(HttpStatus.BAD_REQUEST, "이미 사용 중인 이메일입니다."),
    ITEM_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 상품입니다.");

    private final HttpStatus status;
    private final String message;

    ErrorCode(HttpStatus status, String message) {
        this.status = status;
        this.message = message;
    }
}

이 코드에서는 에러 코드를 enum으로 관리한다.

에러 코드를 enum으로 관리하면 에러 메시지가 코드 전체에 흩어지지 않고 한 곳에서 관리된다.

나중에 메시지를 바꿀 때 이 파일 하나만 수정하면 되기 때문에 유지보수에 좋다.

BusinessException.java 만들기

package com.seongmo.myshop.exception;

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {
    private final ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
}

이 코드는 비즈니스 로직에서 발생하는 예외를 담당하는 커스텀 예외 클래스이다.

RuntimeException 을 상속받는 이유는 Spring에서 RuntimeException 이 발생하면 자동으로 트랜잭션이 롤백되기 때문이다.

ErrorResponse.java 만들기

에러 응답 형식을 정의해보자.

package com.seongmo.myshop.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ErrorResponse {
    private int status;
    private String message;
}

GlobalExceptionHandler.java 만들기

package com.seongmo.myshop.exception;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        ErrorCode errorCode = e.getErrorCode();
        return ResponseEntity
                .status(errorCode.getStatus())
                .body(new ErrorResponse(
                        errorCode.getStatus().value(),
                        errorCode.getMessage()
                ));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        return ResponseEntity
                .status(500)
                .body(new ErrorResponse(500, "서버 오류가 발생했습니다."));
    }
}

이 코드에서 애플리케이션 전체에서 발생하는 예외를 모두 처리한다.

  • @RestControllerAdvice 가 모든 컨트롤러에서 발생하는 예외를 가로채서 처리한다.
  • @ExceptionHandler 는 특정 예외 클래스가 발생했을 때 실행할 메서드를 지정한다.
  • BusinessException 이 발생하면 첫 번째 메서드가 실행되고, 그 외 예상치 못한 예외는 두 번째 메서드가 처리한다.

기존 코드 수정

MemberService.java

@Transactional
public MemberJoinResponse join(MemberJoinRequest request) {
    if (memberRepository.existsByEmail(request.getEmail())) {
        throw new BusinessException(ErrorCode.DUPLICATE_EMAIL);  // 변경
    }

    Member member = new Member(
            request.getEmail(),
            request.getPassword(),
            request.getNickname()
    );

    Member savedMember = memberRepository.save(member);
    return new MemberJoinResponse(
            savedMember.getId(),
            savedMember.getEmail(),
            savedMember.getNickname()
    );
}

@Transactional(readOnly = true)
public MemberJoinResponse getMember(Long id) {
    Member member = memberRepository.findById(id)
            .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));  // 변경
    return new MemberJoinResponse(
            member.getId(),
            member.getEmail(),
            member.getNickname()
    );
}

기존에 IllegalArgumentException 으로 던지던 예외를 BusinessException 으로 교체했다.

ItemService.java

@Transactional
public ItemResponse createItem(ItemCreateRequest request) {
    Member seller = memberRepository.findById(request.getMemberId())
            .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));  // 변경

    Item item = new Item(
            request.getTitle(),
            request.getDescription(),
            request.getPrice(),
            seller
    );

    Item savedItem = itemRepository.save(item);
    return new ItemResponse(savedItem);
}

@Transactional(readOnly = true)
public ItemResponse getItem(Long id) {
    Item item = itemRepository.findById(id)
            .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND));  // 변경
    return new ItemResponse(item);
}

기존에 IllegalArgumentException 으로 던지던 예외를 BusinessException 으로 교체했다.

실행 및 확인

수정한 에러 코드와 메시지가 잘 반환되는 것을 알 수 있다.

저작자표시 (새창열림)

'Backend > Spring' 카테고리의 다른 글

[Backend/Spring] Spring Boot - 테스트 코드(JUnit5 / Mockito)  (0) 2026.03.31
[Backend/Spring] Spring Boot - Spring Security + JWT  (0) 2026.03.31
[Backend/Spring] Spring Boot - @OneToMany 와 N+1 문제  (0) 2026.03.29
[Backend/Spring] Spring Boot - JPA 연관관계  (0) 2026.03.29
[Backend/Spring] Spring Boot - JPA와 MySQP 연동  (0) 2026.03.29
'Backend/Spring' 카테고리의 다른 글
  • [Backend/Spring] Spring Boot - 테스트 코드(JUnit5 / Mockito)
  • [Backend/Spring] Spring Boot - Spring Security + JWT
  • [Backend/Spring] Spring Boot - @OneToMany 와 N+1 문제
  • [Backend/Spring] Spring Boot - JPA 연관관계
Study with Me!
Study with Me!
Study with Me!
  • Study with Me!
    Seongmo
    Study with Me!
  • 전체
    오늘
    어제
    • Computer (147) N
      • Computer Science (61)
        • Data Structure (51)
        • Algorithm (6)
        • 선형대수 with C++ (4)
      • Backend (11) N
        • 백엔드 취업을 위해.. (1)
        • Spring (10) N
        • Database (0)
        • Testing (0)
        • Infra & DevOps (0)
      • Arm Architecture (1)
        • Register (0)
        • Assembly Instruction (1)
      • Linux (32)
        • Linux Kernel (4)
        • 라이브러리 함수 구현하기 (0)
        • 쉘, 쉘 명령어 구현하기 (15)
        • Ubuntu (13)
      • Cloud Infrastructure (8)
        • Kubernetes (7)
        • OpenStack Magnum (1)
      • AWS (3)
      • Baekjoon (18)
      • Tools (6)
        • Git & Github (5)
        • Vim (1)
      • 개발 환경 (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    STL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Study with Me!
[Backend/Spring] Spring Boot - 예외 처리
상단으로

티스토리툴바