Spring으로 회원 조회 API를 구현하면서 존재하지 않는 회원을 조회했을 때 예외를 어떻게 발생시키고, 발생한 예외를 어떻게 처리하는지 학습했다.
처음에는 IllegalStateException을 사용했다.
@Transactional(readOnly = true)
public GetMemberResponse getOne(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(
() -> new IllegalStateException("없는 멤버입니다.")
);
return new GetMemberResponse(
member.getId(),
member.getName(),
member.getAge(),
member.getMbti()
);
}
회원이 존재하지 않는다면 orElseThrow()를 통해 예외가 발생한다.
그런데 여기서 중요한 점!
예외를 발생시키는 것과 예외를 처리하는 것은 다르다.
orElseThrow()는 예외를 던지는 역할을 할 뿐,
클라이언트에게 어떤 HTTP 상태코드를 반환할지 등의 처리를 직접 해주는 것은 아니다.
1. Custom Exception이란?
기존에는 Java에서 제공하는 IllegalStateException을 사용했다.
throw new IllegalStateException("없는 멤버입니다.");
하지만 IllegalStateException이라는 이름만으로는 왜 예외가 발생했는지 명확하게 알기 어렵다.
이번 경우는 정확히 말하면 회원(Member)을 찾을 수 없어서 발생한 예외이다.
그래서 해당 상황을 명확하게 표현하는 커스텀 예외를 직접 만들었다.
public class MemberNotFoundException extends IllegalStateException {
public MemberNotFoundException(String message) {
super(message);
}
}
2. 직접 만든 Custom Exception 사용하기
기존의 IllegalStateException을 직접 만든 MemberNotFoundException으로 변경했다.
@Transactional(readOnly = true)
public GetMemberResponse getOne(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(
() -> new MemberNotFoundException("없는 멤버입니다.")
);
return new GetMemberResponse(
member.getId(),
member.getName(),
member.getAge(),
member.getMbti()
);
}
흐름은 다음과 같다.
memberRepository.findById(999)
↓
회원이 존재하지 않음
↓
Optional.empty()
↓
orElseThrow()
↓
MemberNotFoundException 발생
이제 예외의 이름만 봐도 Member를 찾지 못해서 발생한 문제라는 것을 알 수 있다.
3. 예외 발생과 예외 처리의 차이
.orElseThrow(
() -> new MemberNotFoundException("없는 멤버입니다.")
);
여기까지 작성했다고 해서 직접적인 예외 처리가 끝난 것은 아니다.
throw
→ 예외라는 공을 던진다.
ExceptionHandler
→ 날아온 예외를 받아서 처리한다.
현재 Service는 MemberNotFoundException을 발생시키는 역할을 한다.
이 예외를 받아서 HTTP 응답으로 변환해주는 별도의 처리가 필요하다.
4. @RestControllerAdvice로 예외 처리하기
Controller에서 발생하여 올라오는 예외를 공통적으로 처리하기 위해 @RestControllerAdvice를 사용할 수 있다.
@RestControllerAdvice
public class MemberExceptionHandler {
}
쉽게 생각하면 @RestControllerAdvice는 여러 Controller에서 발생한 예외를 받아 처리하는 중앙 예외 처리 담당자와 비슷하다.
그리고 어떤 예외를 처리할 것인지는 @ExceptionHandler로 지정할 수 있다.
@ExceptionHandler(MemberNotFoundException.class)
MemberNotFoundException이 발생하면 이 메서드에서 처리하겠다라는 의미
@Slf4j
@RestControllerAdvice
public class MemberExceptionHandler {
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<String> handleMemberNotFound(
MemberNotFoundException e
) {
log.error("[ERROR] MemberNotFoundException 발생", e);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(e.getMessage());
}
}
5. Stack Trace란?
예외가 발생했을 때 단순히 로그에
없는 멤버입니다.
만 출력된다면 어디에서 예외가 발생했는지 찾기가 어렵다.
Stack Trace는 예외가 발생한 위치와 해당 위치까지 어떤 메서드들이 호출되었는지를 보여준다.
MemberNotFoundException: 없는 멤버입니다.
at MemberService.getOne(MemberService.java:...)
at MemberController.getMember(MemberController.java:...)
...
log.error("[ERROR] MemberNotFoundException 발생", e);
두 번째 인자로 예외 객체 e 자체를 전달하면 예외 메시지뿐 아니라 Stack Trace도 함께 로그에 출력된다.
log.error("[ERROR] {}", e.getMessage());
메시지만 출력하면 전체 Stack Trace는 남지 않는다.
오늘의 핵심 정리
1. Custom Exception
→ 상황에 맞는 예외를 직접 정의할 수 있다.
2. orElseThrow()
→ 예외를 발생시킨다.
3. @RestControllerAdvice
→ Controller에서 발생해 올라온 예외를 공통 처리할 수 있다.
4. @ExceptionHandler
→ 어떤 예외를 처리할 것인지 지정한다.
5. 예외 발생 ≠ 예외 처리
throw → 던지기
ExceptionHandler → 받아서 처리하기
6. Stack Trace
→ 예외가 어디에서 발생했고 어떤 호출 경로를 거쳤는지 보여준다.
7. log.error("...", e)
→ ERROR 로그와 Stack Trace를 함께 남길 수 있다.
'Spring' 카테고리의 다른 글
| [Spring] JPA 연관관계 매핑 정리 (0) | 2026.07.06 |
|---|---|
| [Spring] Session과 JWT란? (0) | 2026.07.03 |
| [Spring] 핵심 개념 정리 (Bean, IoC, DI) (0) | 2026.07.02 |
| [Spring] Request DTO는 스프링이 만들어주는데, Response DTO는 왜 내가 만들어야 할까? (0) | 2026.07.01 |
| [Spring 입문] 스프링 전체 흐름 정리 (0) | 2026.06.29 |