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

없는 회원에 대한 정보를 요청했기 때문에 에러가 발생할 것은 당연히 예상할 수 있다.
그치만 여기에 2가지 문제가 있다.
- "status" 가 500(서버 오류)인데 지금은 서버 오류가 아니고 클라이언트가 잘못된 ID를 요청한 것이므로 404(클라이언트 오류)여야 한다.
- 에러 메시지가 없어서 클라이언트 입장에서 뭐가 문제인지 알 수 없다.
커스텀 예외 클래스 만들기
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 |