// 특정 컨텐츠의 모든 쿼리 조회
@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 클래스 안의 메서드에 이 어노테이션을 붙인다.
- "이 메서드는 처리할_예외_클래스라는 특정 유형의 환자(예외)를 전문적으로 담당하는 의사"라고 지정하는 것이다.
처리 흐름:
- 컨트롤러나 서비스 등 코드 어느 곳에서든 예외가 발생한다.
- 코드는 예외를 직접 처리하지 않고 그냥 던진다(throw).
- @ControllerAdvice가 던져진 예외를 가로챈다.
- 가로챈 예외의 종류에 맞는 @ExceptionHandler 메서드를 찾아 실행한다.
- 해당 메서드는 일관된 형식의 오류 응답(예: 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 |