Back-End

[Spring boot] 중앙 집중식 예외 처리

Minch13r 2025. 8. 14. 13:49
// 특정 컨텐츠의 모든 쿼리 조회
    @Transactional(readOnly = true)
    public List<NoSqlQuery> findQueriesByContent(Long contentId) {
        // content 검증
        Content content = getContentService.getByIdIfPermitted(contentId);

        // 해당 콘텐츠의 모든 쿼리 반환
        return noSqlQueryRepository.findByContent(content);
    }

 

해당 코드는 특정 컨텐츠에 해당 가능한 즉, 매핑된 모든 쿼리를 조회하는 간단한 메서드이다.

 

1차로 이 쿼리가 특정 컨텐츠에 해당되는지 검증하고, 검증에 통과하면 모든 쿼리를 List<NoSqlQuery>로 반환한다.

 

여기서 나는 쿼리가 컨텐츠에 해당하지 않을 경우, 에러를 정해두거나 다른 값을 반환해야 하지 않을까 라는 생각이 들었다.

 

@Transactional(readOnly = true)
public List<NoSqlQuery> findQueriesByContent(Long contentId) {
    try {
        // 1. content 검증 시도
        Content content = getContentService.getByIdIfPermitted(contentId);

        // 2. 검증 성공 시, 해당 콘텐츠의 모든 쿼리 반환
        return noSqlQueryRepository.findByContent(content);

    } catch (EntityNotFoundException e) {
        // 3. contentId에 해당하는 콘텐츠가 없을 때 예외 발생
        log.error("쿼리 목록 조회 실패: 유효하지 않은 컨텐츠 ID입니다. contentId={}, error={}", contentId, e.getMessage());
        
        throw e; 
    }
}

 

Service Layer에 있는 메서드이기에 이런식으로 예외처리가 가능하다.

 

이번 포스트의 핵심은 Spring boot의 중앙 집중식 예외 처리 이다.

 

중앙 집중식 예외 처리는 애플리케이션의 모든 부분에서 발생하는 오류(예외)를 한 곳에서 통합하여 관리하는 프로그래밍 방식이다.

 

쉽게 말해, 코드 곳곳에 try-catch 문을 놓는 대신, 예외 처리 전담팀을 하나 만들어 모든 예외를 전담팀에게 넘긴다고 보면 된다.


비유

 

  • 기존 방식 (개별 처리): 애플리케이션의 모든 기능(메서드)이 각자 작은 구급상자를 가지고 있는 것과 같다. 작은 상처(예외)가 날 때마다 스스로(try-catch)해야 한다. 기능마다 치료 방식이 다를 수 있고, 구급상자가 없는 곳에서는 대처가 불가능하다.

 

  • 중앙 집중식 방식: 애플리케이션 전체에 하나의 크고 전문적인 응급실을 두는 것이다. 어디에서 어떤 환자(예외)가 발생하든 즉시 이 응급실로 보낸다. 그러면 응급실의 전문의가 환자의 종류(예외의 종류)에 따라 일관되고 전문적인 방식으로 처리한다.

 


코드 예시

@Slf4j
@RestControllerAdvice // 모든 @RestController에서 발생하는 예외를 처리
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class) // EntityNotFoundException이 발생하면 이 메서드가 실행됨
    public ResponseEntity<ErrorResponse> handleEntityNotFoundException(EntityNotFoundException e) {
        log.error("대상을 찾지 못했습니다: {}", e.getMessage());
        // 사용자에게 보여줄 에러 응답을 생성하여 404 상태 코드와 함께 반환
        ErrorResponse response = new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }
}

 

이와 같이 Service Laeyr에 있는 모든 메서드가 EntityNotFoundException이 발생하면  GlobalExceptionHandler가 중간에서 에러 메세지를 낚아채 handleEntityNotFoundException을 발생시킬 수 있도록 한다.

 

에러 반환은 이 메서드만 수정하면 될 것이고, Service Layer는 예외 처리를 줄임으로써 한층 더 가벼워질 수 있다.


동작 방식

 

  • @ControllerAdvice 또는 @RestControllerAdvice
    • 이 어노테이션을 붙이면 "이 클래스는 우리 애플리케이션의 예외를 전담 처리하는 응급실"라고 선언하는 것과 같다.
  • @ExceptionHandler(처리할_예외_클래스.class)
    • @ControllerAdvice 클래스 안의 메서드에 이 어노테이션을 붙인다.
    • "이 메서드는 처리할_예외_클래스라는 특정 유형의 환자(예외)를 전문적으로 담당하는 의사"라고 지정하는 것이다.

처리 흐름:

  1. 컨트롤러나 서비스 등 코드 어느 곳에서든 예외가 발생한다.
  2. 코드는 예외를 직접 처리하지 않고 그냥 던진다(throw).
  3. @ControllerAdvice가 던져진 예외를 가로챈다.
  4. 가로챈 예외의 종류에 맞는 @ExceptionHandler 메서드를 찾아 실행한다.
  5. 해당 메서드는 일관된 형식의 오류 응답(예: JSON 에러 메시지와 404 상태 코드)을 사용자에게 반환한다.

장점 :

 

  • 코드 분리 (관심사의 분리): 비즈니스 로직을 처리하는 코드와 예외를 처리하는 코드가 명확하게 분리된다. 서비스 코드는 자신의 핵심 기능에만 집중할 수 있어 훨씬 깔끔해진다.
  • 중복 제거 (DRY): 모든 메서드에 try-catch를 반복해서 작성할 필요가 없다. 예외 처리 로직이 단 한 곳에만 존재하므로 코드 중복이 사라진다.
  • 일관성 있는 처리: 모든 오류가 동일한 방식으로 처리되므로, 사용자나 다른 개발자는 일관된 형태의 오류 응답을 받게 되어 신뢰성이 높아진다.
  • 유지보수 용이: 예외 처리 정책이 변경될 경우, 여러 파일을 수정할 필요 없이 '응급실' 역할을 하는 단 하나의 클래스만 수정하면 된다.

 

간단한 방식이지만, 개발할 때 이 방식을 사용하면 편리하다.

 

개발할 때만 보고 코딩하는 것이 아니라, 나중에 유지보수 할 것까지 염두에 두고 개발을 한다면 더 편리한 서비스를 운영할 수 있다.

 

 

'Back-End' 카테고리의 다른 글

LDAP  (3) 2025.09.02
[Spring boot] Transactional의 함정  (1) 2025.08.15
동기와 비동기  (1) 2025.05.30
[Redis] 기본 개념  (1) 2025.05.26
[Java] JPA 영속성  (1) 2025.05.23