2025. 6. 2. 06:31ㆍ코딩 도구/백엔드 개발 (Backend Development)
자바 예외 이해 정리
이 글은 인프런 김영한님의 "스프링 DB 1편 - 데이터 접근 핵심 원리" 강의를 수강하고 정리한 내용이다.
이번 글에서는 자바 예외의 기본 개념부터 시작해, 체크 예외와 언체크 예외의 차이, 각각의 장단점, 그리고 실무에서 자주 사용되는 예외 처리 전략까지 상세하게 정리한다.
1. 자바 예외 계층 구조 이해
예외는 자바 언어의 핵심 문법 중 하나로, 모든 예외는 Throwable을 기준으로 계층 구조를 가진다.
- Object: 모든 클래스의 최상위 부모
- Throwable: 예외와 오류의 최상위 부모
- Error: 메모리 부족, 시스템 장애 등 복구 불가능한 예외
- Exception: 개발자가 처리 가능한 예외
- RuntimeException: 언체크 예외 (컴파일러가 강제하지 않음)
- 그 외: 체크 예외 (컴파일러가 강제함)
주의할 점: Throwable을 catch로 잡으면 Error까지 함께 잡게 되므로 애플리케이션 로직에서는 Exception만 처리하는 것이 좋다.
2. 예외 처리 기본 규칙
- 예외는 반드시 잡거나 던져야 한다.
- 상위 타입으로 예외를 catch 또는 throws하면 하위 타입도 함께 포함된다.
예외를 처리하지 않고 계속 던지면, 최종적으로 main()이나 웹 서버(WAS)에 도달하게 되고, 이 경우 서버는 적절한 오류 페이지를 응답하게 된다.
3. 체크 예외 (Checked Exception)
Exception을 상속한 예외는 기본적으로 체크 예외이다. 컴파일러는 이 예외를 강제적으로 처리하게 만든다.
static class MyCheckedException extends Exception {
public MyCheckedException(String message) {
super(message);
}
}
처리 방법
- 예외를 직접 잡는 경우
try {
repository.call();
} catch (MyCheckedException e) {
log.info("예외 처리, message={}", e.getMessage(), e);
}
- 예외를 밖으로 던지는 경우
public void callThrow() throws MyCheckedException {
repository.call();
}
throws를 생략하면 컴파일 오류가 발생한다.
장단점
- 장점: 컴파일러가 예외 누락을 막아준다 (안전 장치)
- 단점: 너무 많은 예외를 강제로 처리해야 하므로 번거롭다. 특히 복구 불가능한 예외도 일일이 선언해야 한다.
의존성 문제
체크 예외를 던지면 해당 예외 타입을 상위 계층에서도 의존하게 된다. 예를 들어 SQLException을 던지면 Controller까지 java.sql에 의존하게 되어 기술 변경 시 파급효과가 커진다.
4. 언체크 예외 (Unchecked Exception)
RuntimeException을 상속한 예외는 언체크 예외이며, 컴파일러가 강제하지 않는다.
static class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
처리 방법
- 잡아서 처리 가능
try {
repository.call();
} catch (MyUncheckedException e) {
log.info("예외 처리", e);
}
- 던지기 (선언 생략 가능)
public void callThrow() {
repository.call();
}
장단점
- 장점: 예외 선언 없이 간단하게 처리 가능, 예외 의존성 제거, 구현 기술 변경 시 유연함
- 단점: 실수로 예외를 놓치기 쉬움 → 문서화, throws 명시로 보완 필요
5. 실무에서의 체크 예외 문제점
1. 복구 불가능한 예외까지 강제로 처리해야 함
- DB 장애나 네트워크 오류 등은 대부분 복구 불가능한 예외인데, 체크 예외이기 때문에 무조건 선언 필요
2. 의존성 전파
- JDBC에서 SQLException을 던지면, Controller, Service 계층까지 모두 이 예외를 의존해야 하며, 기술 전환 시 (예: JPA) 전 계층 수정 필요
3. throws Exception의 위험성
- throws Exception을 선언하면 모든 예외를 던지는 것이 되므로, 중요한 예외가 누락될 수 있음
6. 언체크 예외 전략 (예외 전환)
실무에서는 주로 체크 예외 → 언체크 예외로 전환해서 사용한다.
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e); // 기존 예외 포함
}
이렇게 하면 Repository 계층은 SQL에 의존해도 되고, Service/Controller는 예외를 선언하지 않아도 된다.
class RuntimeSQLException extends RuntimeException {
public RuntimeSQLException(Throwable cause) {
super(cause);
}
}
기존 예외 포함의 중요성
- 예외 전환 시 원래 예외를 꼭 포함시켜야 로그에 정확한 스택 트레이스가 남는다.
log.info("에러 발생", e); // 마지막 인자로 예외 전달
7. 예외 전환과 문서화
언체크 예외를 사용할 때는 문서화를 잘 해야 한다. 또는 throws에 명시적으로 선언해서 IDE나 문서에서 확인 가능하게 만들어야 한다.
예시 - 스프링 JdbcTemplate:
void execute(String sql) throws DataAccessException;
예시 - JPA EntityManager:
void persist(Object entity) throws EntityExistsException, IllegalArgumentException, ...
런타임 예외도 잘 명시하면 호출자가 어떤 예외가 발생할지 유추할 수 있다.
8. 정리 및 실무 전략
체크 예외 | 반드시 잡거나 던져야 하며 컴파일러가 강제함 | SQLException, IOException 등 |
언체크 예외 | 선언하지 않아도 자동으로 던져짐 | NullPointerException, IllegalArgumentException 등 |
실무 전략 요약
- 대부분의 경우 언체크 예외를 기본으로 사용한다.
- 복구 가능한 예외나 반드시 잡아야 할 경우에만 체크 예외를 사용한다.
- 예외 전환 시에는 반드시 기존 예외를 포함한다.
- 문서화 또는 throws로 던지는 예외를 명시해 협업 시 유용하게 만든다.
'코딩 도구 > 백엔드 개발 (Backend Development)' 카테고리의 다른 글
[Spring DB] 스프링과 트랜잭션 문제 해결 정리 (0) | 2025.05.26 |
---|---|
[Spring DB] 트랜잭션 이해 (0) | 2025.05.20 |
[Spring DB] 커넥션 풀과 DataSource (0) | 2025.05.12 |
[Spring DB] JDBC 이해 (0) | 2025.05.05 |
[Spring] 파일 업로드 정리 (0) | 2025.04.28 |