๐Ÿ“Œ์˜ˆ์™ธ์ฒ˜๋ฆฌ

leejihyeonยท2026๋…„ 4์›” 21์ผ

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
1/2

์ง€๊ธˆ๊นŒ์ง€ ์‚ฌ์šฉํ•œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋ฐฉ์‹

// MemberController
@GetMapping("/members/{memberId}")
public ResponseEntity<GetMemberResponse> getMember(
        @PathVariable Long memberId
) {
    return ResponseEntity.status(HttpStatus.OK).body(memberService.findOne(memberId));
}
  • ์กฐํšŒ๊ฐ€ ์ž˜ ๋˜๋ฉด ok๋ฅผ ๋‚ ๋ฆฐ๋‹ค.
// MemberService
@Transactional(readOnly = true)
public GetMemberResponse findOne(Long memberId) {
    Member member = memberRepository.findById(memberId).orElseThrow(
            () -> new IllegalStateException("์—†๋Š” ๋ฉค๋ฒ„์ž…๋‹ˆ๋‹ค.")
    );
    return new GetMemberResponse(
            member.getId(),
            member.getNickname()
    );
}
  • ์กฐํšŒํ•  id๊ฐ€ ์—†๋‹ค๋ฉด IllegalStateException("์—†๋Š” ๋ฉค๋ฒ„์ž…๋‹ˆ๋‹ค.") ๋ฅผ ๋‚ ๋ ค์ค€๋‹ค.

์—ฌ๊ธฐ์„œ ์ผ๋ถ€๋Ÿฌ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด

  • 500์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋ฉค๋ฒ„๋ฅผ ์ž˜๋ชป ํ˜ธ์ถœํ•œ ๊ฒƒ์€ ์„œ๋ฒ„์˜ ์ž˜๋ชป์ด ์•„๋‹Œ ํด๋ผ์ด์–ธํŠธ์˜ ์ž˜๋ชป์ด๊ธฐ ๋•Œ๋ฌธ์— 500์ด ์•„๋‹Œ 400์—๋Ÿฌ ์ฝ”๋“œ๊ฐ€ ๋– ์•ผ ํ•œ๋‹ค.

  • ์ž๋ฐ”์˜ try-catch๋ฌธ์œผ๋กœ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์Šคํ”„๋ง์—์„œ๋Š” ์—๋Ÿฌ๋ฅผ 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šคํ”„๋ง ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต 3๊ฐ€์ง€
1. @ExceptionHandler
2. @RestControllerAdvice
3. Spring ๊ธฐ๋ณธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

์ด๋ ‡๊ฒŒ 3๊ฐ€์ง€๊ฐ€ ์žˆ๊ณ  ๋™์‹œ์— ์กด์žฌํ•  ๊ฒฝ์šฐ 1 > 2 > 3 ์ˆœ์„œ๋Œ€๋กœ ์šฐ์„  ์ˆœ์œ„๋ฅผ ๊ฐ€์ง„๋‹ค.


1. @ExceptionHandler - ์ปจํŠธ๋กค๋Ÿฌ๋ณ„ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.
@Service
@RequiredArgsConstructor
public class MemberService {

  @Transactional
      public GetAllMemberResponseDto findOne(Long id) {
          Member member = memberRepository.findById(id).orElseThrow(
                  () -> new IllegalStateException("์—†๋Š” ๋ฉค๋ฒ„์ž…๋‹ˆ๋‹ค.")
          );
          return GetAllMemberResponseDto.from(member);
      }
@RestController
@RequiredArgsConstructor
public class MemberController {
    
    private final MemberService memberService;
        
    @GetMapping("/members/{memberId}")
    public ResponseEntity<GetMemberResponse> getMember(
            @PathVariable Long memberId
    ) {
        return ResponseEntity.status(HttpStatus.OK).body(memberService.findOne(memberId));
    }
        
    // ๐Ÿšจ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handlerIllegalStateException(IllegalStateException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body("์š”์ฒญ ์˜ค๋ฅ˜: " + e.getMessage());
    }
}

  • ์š”์ฒญ ์˜ค๋ฅ˜: : ์„œ๋น„์Šค์—์„œ ๋˜์ง„ IllegalStateException ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์ธ "์—†๋Š” ๋ฉค๋ฒ„์ž…๋‹ˆ๋‹ค."๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ์˜ ResponseEntity์— ์„ค์ •ํ•œ ๋Œ€๋กœ (.status(HttpStatus.BAD_REQUEST) 400 Bad Request๋กœ ์—๋Ÿฌ ์ฝ”๋“œ๊ฐ€ ์‘๋‹ต๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ๋งˆ๋‹ค @ExceptionHandler๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด์œ ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋งˆ๋‹ค ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ค‘๋ณต์ด ์‹ฌํ•ด์ง„๋‹ค.

์ค‘๋ณต์ด ์‹ฌํ•ด์ง„๋‹ค๋Š” ๋ง์€ ๋ฉค๋ฒ„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @ExceptionHandler๊ฐ€ ์žˆ๊ณ  ์ผ์ • ์ปจํŠธ๋กค๋Ÿฌ์—๋„ @ExceptionHandler์ด ์žˆ์œผ๋ฉด ๋‚˜์ค‘์— ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ƒ๊ธฐ๋ฉด ์ƒ๊ธธ์ˆ˜๋ก ์ค‘๋ณต์ด ์‹ฌํ•ด์ง„๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

๋”ฐ๋ผ์„œ ํ”„๋กœ์ ํŠธ์— ์ „์ฒด์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์ž‡๋Š” ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด ์ฒ˜๋ฆฌํ•˜๋ฉด ๋œ๋‹ค.

๊ทธ๊ฒƒ์ด @RestControllerAdvice ๋ฐฉ์‹์ด๋‹ค. (์ด ๋ฐฉ์‹์ด ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค.)

2. @RestControllerAdvice - ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ(์ค‘์š”!)

  • ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์˜ ์˜ˆ์™ธ๋ฅผ ํ•œ ๊ณณ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // ๐ŸŽฏ ์ปค์Šคํ…€ ๋น„์ฆˆ๋‹ˆ์Šค ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<String> handleIllegalStateException(IllegalStateException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body("์š”์ฒญ ์˜ค๋ฅ˜: " + e.getMessage());
    }
}
  • ํ”„๋กœ์ ํŠธ memberํŒจํ‚ค์ง€์•ˆ์— configํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค์–ด GlobalExceptionHandler ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
  • ์ด์ฒ˜๋Ÿผ @RestControllerAdvice ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•œ๋‹ค๋ฉด, ์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ „์—ญ์ ์œผ๋กœ IllegalStateException ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ƒํ™ฉ์— ๋Œ€ํ•ด ๋ชจ๋“  API ์‘๋‹ต์— ๊ณตํ†ต๋œ ์—๋Ÿฌ ์‘๋‹ต์„ ๋˜์ง„๋‹ค.

3. ์ปค์Šคํ…€ ์—๋Ÿฌ

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

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ƒํ™ฉ์— ๋”ฑ ๋งž๋Š” ์˜ˆ์™ธ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•œ๋‹ค.

  1. ๋จผ์ € RuntimeException์„ ์ƒ์†๋ฐ›์•„์„œ ์šฐ๋ฆฌ๋งŒ์˜ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.
package com.example.scheduledevelop.exception;

import org.springframework.http.HttpStatus;

public class ScheduleNotFoundException extends RuntimeException {


    public ScheduleNotFoundException(String message) {
        super(message);
    }
}

RuntimeException์„ ์ƒ์†ํ•ด์•ผ ํ•œ๋‹ค.


@Transactional(readOnly = true)
    public FindByOneResponseDto findOne(Long id) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                () -> new ScheduleNotFoundException("์—†๋Š” ์ผ์ •์ž…๋‹ˆ๋‹ค.")
        );
        return FindByOneResponseDto.from(schedule);
    }
  • ์„œ๋น„์Šค ํด๋ž˜์Šค์—์„œ ์—๋Ÿฌ๋ฅผ ๋˜์ง€๋Š” ๋ถ€๋ถ„์„ ๊ธฐ๋ณธ ์—๋Ÿฌ๊ฐ€ ์•„๋‹Œ ๋‚ด๊ฐ€ ๋งŒ๋“  ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ๋˜์ ธ์ค€๋‹ค.
  • ์ด์ œ GlobalExceptionHandler๋กœ ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ํ•ธ๋“œ๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์— ๊ฐ™์ด ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.

  • ์ด๋ฆ„์ด Global์ธ๋ฐ Globalํ•˜์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

  • ์•ž์œผ๋กœ ScheduleNotFoundException๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์•ž์œผ๋กœ ์ˆ˜๋ฐฑ ์ˆ˜์ฒœ๊ฐ€์ง€ ์ด์ƒ์˜ ์ปค์Šคํ…€ ์—๋Ÿฌ๊ฐ€ ์ƒ๊ธธ ํ…๋ฐ, ๊ทธ ๋•Œ๋งˆ๋‹ค GlobalExceptionHandler์— ํ•˜๋‚˜ํ•˜๋‚˜ ๋”ฐ๋กœ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ์ค˜์•ผ ํ•œ๋‹ค.

  • ์œ„์˜ ์ฝ”๋“œ๋ณด๋ฉด HttpsStatus.Not_Found๋กœ ๊ณ ์ •๋˜์–ด์žˆ๊ธฐ๋„ํ•˜๋‹ค.

  • ์ด๋ฅผ ์œ„ํ•ด ๊ณตํ†ต๋œ ๋ถ€๋ชจ ์—๋Ÿฌ ํƒ€์ž…์„ ํ•˜๋‚˜ ๋”์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

๊ณตํ†ต ๋ถ€๋ชจ ์ปค์Šคํ…€ ์—๋Ÿฌ ํƒ€์ž… ๋งŒ๋“ค๊ธฐ

package com.example.scheduledevelop.exception;

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

@Getter
public class ServiceException extends RuntimeException {

    // ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ฐ˜ํ™˜ํ•  HTTP ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ
    private final HttpStatus status;

    public ServiceException(HttpStatus status, String message) {
        super(message);
        this.status = status;
    }
}
  • ๊ณตํ†ต ์ƒ์œ„ ์ปค์Šคํ…€ ์—๋Ÿฌ ํด๋ž˜์Šค
  • ServiceExcetion์ด๋ผ๋Š” ๊ณตํ†ต ๋ถ€๋ชจ๋ฅผ ๋‘์–ด ์ƒํƒœ ์ฝ”๋“œ(HttpStatus)๋ฅผ ๊ด€๋ฆฌํ•˜๊ฒŒ ํ•ด์„œ ์ƒˆ๋กฑ๋ฃฌ ์˜ˆ์™ธ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด๋„ GlobalExceptionHandler๋ฅผ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

package com.example.scheduledevelop.exception;

import org.springframework.http.HttpStatus;

public class ScheduleNotFoundException extends ServiceException {

    public ScheduleNotFoundException(String message) {
        super(HttpStatus.NOT_FOUND, message);
    }
}
  • ServiceException์„ ์ƒ์†ํ•ด์•ผ ํ•œ๋‹ค.
  • ์ด ์ฝ”๋“œ๋Š” ๊ตฌ์ฒด์ ์ธ ์—๋Ÿฌ๋กœ ์ผ์ • id๋ฅผ ๋ชป์ฐพ์•˜์„ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.
  • ๋ถ€๋ชจ์—๊ฒŒ 404(NOT_FOUND) ์ƒํƒœ๋ฅผ ๊ณ ์ •์ ์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.

@Transactional(readOnly = true)
    public FindByOneResponseDto findOne(Long id) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                () -> new ScheduleNotFoundException("์—†๋Š” ์ผ์ •์ž…๋‹ˆ๋‹ค.")
        );
        return FindByOneResponseDto.from(schedule);
    }
  • ์—๋Ÿฌ๋ฅผ ๋˜์ง€๋Š” ์ฝ”๋“œ ๋ถ€๋ถ„์—์„œ ์ƒˆ๋กœ ๋งŒ๋“  ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ๋˜์ง„๋‹ค.

@RestControllerAdvice
public class GlobalExceptionHandler {
 @ExceptionHandler(ServiceException.class)
    public ResponseEntity<String> handleServiceException(ServiceException e) {
        return ResponseEntity
                .status(e.getStatus())
                .body(e.getMessage());
    }
  • GlobalExceptionHandle๋กœ ์ปค์Šคํ…€ ์—๋Ÿฌ๋“ค์„ ๋‹จ ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๋กœ ๋ชจ๋‘ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

// extends ServerException ์ค‘์š”!
public class DuplicateEmailException extends ServiceException {
    public DuplicateEmailException(String message) {
        super(HttpStatus.BAD_REQUEST, message);
    }
}
  • DuplicateEmailException(์ด๋ฉ”์ผ ์ค‘๋ณต ์—๋Ÿฌ)์ด๋ผ๋Š” ์—๋Ÿฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ 400์œผ๋กœ ๋ฆฌํ„ดํ•˜๊ฒŒ ํ•˜๊ณ  DuplicateEmailException์„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€