Post
하루 학습: Java에서 예외 처리와 트랜잭션 경계를 실무형으로 설계하기
Java 백엔드에서 장애 대응 품질은 “예외를 어디서 어떻게 처리하느냐”로 크게 갈립니다.
오늘은 Spring 기준으로 예외 처리와 트랜잭션 경계를 실무적으로 정리합니다.
왜 중요한가
-
장애 전파 범위 제어 예외를 잘못 처리하면 단일 요청 실패가 데이터 불일치로 번질 수 있습니다.
-
운영 가시성 확보 에러 코드를 표준화하면 로그/모니터링/알림이 일관돼 원인 파악 시간이 줄어듭니다.
핵심 개념
-
Checked vs Unchecked 예외 전략 도메인 규칙 위반은 커스텀 RuntimeException으로 통일하면 서비스 계층 코드가 단순해집니다.
-
ControllerAdvice로 응답 표준화 예외별 HTTP 상태코드와 메시지 포맷을 중앙에서 통제해야 프론트/모바일 연동이 안정적입니다.
-
트랜잭션 경계는 서비스 계층에
@Transactional은 컨트롤러가 아니라 유즈케이스 단위 서비스 메서드에 두는 게 유지보수에 유리합니다. -
롤백 정책 명시 기본 RuntimeException 롤백 규칙 외에 비즈니스 예외가 있으면
rollbackFor를 명확히 선언합니다.
미니 예제
@Service
@RequiredArgsConstructor
public class EmployeeService {
private final EmployeeRepository employeeRepository;
@Transactional
public Long createEmployee(CreateEmployeeCommand cmd) {
if (employeeRepository.existsByEmail(cmd.email())) {
throw new DomainException("EMPLOYEE_EMAIL_DUPLICATED");
}
Employee employee = Employee.create(cmd.name(), cmd.email());
employeeRepository.save(employee);
return employee.getId();
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DomainException.class)
public ResponseEntity<ApiError> handleDomain(DomainException ex) {
return ResponseEntity.badRequest()
.body(new ApiError(ex.getCode(), "요청 값을 확인해 주세요."));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleUnknown(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiError("INTERNAL_ERROR", "일시적인 오류가 발생했습니다."));
}
}
자주 하는 실수
-
컨트롤러에서 try-catch 남발 계층마다 예외를 중복 처리하면 응답 스펙이 깨지고 누락 케이스가 늘어납니다.
-
트랜잭션 내부에서 외부 API 호출 DB 락 유지 시간이 길어져 성능과 장애 전파 측면에서 모두 불리합니다.
-
에러 코드 미표준화 문자열 메시지로만 처리하면 프론트 분기, 통계, 알림 자동화가 어려워집니다.
오늘의 실습 체크리스트
- 도메인 예외 코드 목록 정리 (
*_NOT_FOUND,*_DUPLICATED등) @RestControllerAdvice로 공통 응답 포맷 통일- 핵심 유즈케이스 1개에
@Transactional경계 명확히 적용 - 운영 로그에 requestId/에러코드/핵심 파라미터 기록
댓글