[JAVA]JAVA실무
[JAVA]실무 try-catch exception 처리
greeniti
2025. 4. 21. 21:18
실무에서는 예외를 단순히 try-catch로 처리하는 것 외에도 유지보수성, 안정성, 로깅 등을 고려한 패턴들이 많이 사용됩니다.
아래에 대표적인 Java 예외 처리 실무 패턴 입니다.
1. 예외 계층 구조 구성 (Custom Exception Hierarchy)
- 공통 예외 슈퍼클래스를 정의하고, 세부 예외를 상속해서 사용
- 예외의 성격(비즈니스, 시스템 등)에 따라 분류
public class AppException extends RuntimeException {
public AppException(String message) {
super(message);
}
}
public class BusinessException extends AppException {
public BusinessException(String message) {
super(message);
}
}
public class SystemException extends AppException {
public SystemException(String message) {
super(message);
}
}
2. 예외 로깅은 반드시 Logger로! (System.out X)
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
try {
// 작업
} catch (Exception e) {
logger.error("예외 발생: {}", e.getMessage(), e);
throw e; // 재던지기
}
SLF4J + Logback 혹은 Log4j2 사용 추천
3. 예외 재던지기 (Wrapping / Rethrowing)
try {
// DB 작업
} catch (SQLException e) {
throw new SystemException("DB 처리 중 오류 발생", e); // 원인 포함
}
예외를 감싸서 비즈니스 계층으로 올리는 방식. 트랜잭션 처리 시 중요.
4. Global Exception Handler (Spring 기준)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<String> handleBusiness(BusinessException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleOther(Exception e) {
return ResponseEntity.internalServerError().body("서버 오류 발생");
}
}
모든 컨트롤러의 예외를 한 곳에서 처리할 수 있어 유지보수가 쉬움
5. 메시지 표준화 (에러코드 + 메시지)
public class ErrorResponse {
private String code;
private String message;
// 생성자, getter/setter
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
return ResponseEntity.badRequest().body(new ErrorResponse("BUSINESS_ERROR", e.getMessage()));
}
클라이언트와 API 연동 시 일관성 있는 응답 제공
6. Optional & Validation 적극 사용 (예외 발생 자체를 줄임)
// Optional로 null 체크 대체
Optional<User> userOpt = userRepository.findById(id);
User user = userOpt.orElseThrow(() -> new BusinessException("사용자 없음"));
// Bean Validation
public class UserDTO {
@NotBlank
private String name;
@Email
private String email;
}
7. 예외를 삼키지 말고 반드시 처리 or 로깅
try {
doSomething();
} catch (Exception e) {
// 이런 건 지양!
// e.printStackTrace(); ← 최소 logger 사용
// 아무 처리 안함 → Silent Fail 위험
}
==> e.printStackTrace()는 가급적 사용 자제...
요약
패턴 설명
계층적 커스텀 예외 | 예외를 역할별로 구분하여 관리 |
로깅 필수 | System.out.println 대신 Logger 사용 |
예외 재포장 | 낮은 수준의 예외를 비즈니스 계층 예외로 변환 |
전역 예외 핸들러 | 예외를 통합적으로 처리 |
응답 메시지 구조화 | API 사용자에게 명확한 에러 전달 |
사전 방어 | Optional, Bean Validation 등으로 예외 자체 줄이기 |
Spring에서 DB insert, update 작업 중 예외 처리를 잘하는 방법은 트랜잭션 처리, 예외 핸들링, 로깅, 그리고 적절한 예외 재던지기를 조합해서 쓰는 겁니다. 아래에 실무에서 자주 쓰는 패턴을 예시 입니다.
✅ 상황: 회원 정보를 DB에 저장 (insert or update)
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public void saveUser(User user) {
try {
if (userRepository.existsByEmail(user.getEmail())) {
throw new BusinessException("이미 등록된 이메일입니다.");
}
userRepository.save(user); // insert or update (JPA 기준)
} catch (DataIntegrityViolationException e) {
// 제약 조건 위반 (예: UNIQUE 키 중복)
throw new BusinessException("데이터 무결성 오류: " + e.getMessage(), e);
} catch (Exception e) {
// 기타 예외
throw new SystemException("회원 저장 중 오류 발생", e);
}
}
}
✅ 예외 계층 예시 (실무 추천)
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
public class SystemException extends RuntimeException {
public SystemException(String message, Throwable cause) {
super(message, cause);
}
}
✅ 전역 예외 처리 (@RestControllerAdvice)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<String> handleBusiness(BusinessException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
@ExceptionHandler(SystemException.class)
public ResponseEntity<String> handleSystem(SystemException e) {
return ResponseEntity.status(500).body("서버 오류가 발생했습니다.");
}
}
✅ 주의할 점
상황 처리 방법
@Transactional | 반드시 Service 계층에 위치해야 함 (AOP 적용 가능 위치) |
DB 제약조건 위반 | DataIntegrityViolationException 또는 ConstraintViolationException catch |
Entity 중복 | insert 전에 existsBy... 또는 DB에서 예외 발생 후 처리 |
업데이트 실패 | 없는 ID로 update 시 예외 → 비즈니스 로직에서 존재 여부 체크 |
롤백 | 예외 발생 시 RuntimeException이면 자동 롤백됨 (@Transactional 기본 동작) |
예시 흐름
- @Transactional 메서드 실행
- Repository.save() 호출
- insert/update 시 예외 발생 가능 (javax.validation, DB 제약 등)
- 예외 catch 후 BusinessException or SystemException으로 재던지기
- @RestControllerAdvice에서 일관된 API 응답 처리