-
예외(Exception) (1)BackEnd/Spring DB 2023. 1. 28. 07:00반응형
예외 계층
- Throwable: 최상위 예외이다. 하위에 Exception과 Error가 있다.
- Error(언체크 예외): 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외이다. 개발자는 이 예외를 잡으려고 해서는 안된다. 상위 예외를 catch로 잡으면 그 하위 예외까지 잡기 때문에 Throwable 예외도 잡으면 안된다.
- Exception(체크 예외): Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. (단, RuntimeException은 예외)
- RuntimeException(언체크 예외, 런타임 예외): RuntimeException과 그 하위 예외는 모두 컴파일러가 체크하지 않는 언체크 예외이다.
예외 기본 규칙
- 예외는 잡아서 처리하거나(try ~ catch) 던져야(throws) 합니다.
- 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리됩니다.
- 예외를 처리하지 못하고 계속 던지면 자바 main() 쓰레드의 경우, 예외 로그를 출력하면서 시스템이 종료됩니다.
체크 예외
Exception을 상속받으면 체크 예외가 됩니다. RuntimeException을 상속받으면 언체크 예외가 됩니다.
static class MyCheckedException extends Exception { public MyCheckedException(String message) { super(message); } }
체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야 합니다. 그렇지 않으면 컴파일 오류가 발생합니다. 체크 예외를 잡아서 처리하려면 catch(..)를 사용해서 예외를 잡으면 됩니다. catch에 예외를 지정하면 해당 예외와 그 하위 타입 예외를 모두 잡아주기에 아래 코드 catch에 Exception을 적어주어도 MyCheckedException을 잡을 수 있습니다.
try { repository.call(); // MyCheckedException 예외 발생 } catch (MyCheckedException e) { //예외 처리 로직 }
체크 예외를 처리할 수 없을 때는 method() throws 예외를 사용해서 밖으로 던질 예외를 필수로 지정해주어야 합니다. 체크 예외를 밖으로 던지는 경우에도 해당 타입과 그 하위 타입을 모두 던질 수 있습니다.
public void callThrow() throws MyCheckedException { repository.call(); // MyCheckedException 예외 발생 }
체크 예외는 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이나, 아래와 같은 문제점을 가지고 있습니다.
1. 복구 불가능한 예외
아래 그림처럼 서비스에서 Repository와 NetworkClient를 호출하였는데 SQLException과 ConnectException이 발생한 경우, 서비스는 이 둘을 처리해야 하지만 ConnectException처럼 연결이 실패하거나, SQLException처럼 데이터베이스에서 발생하는 문제들은 대부분 애플리케이션 로직에서 처리할 방법이 없습니다. 서비스는 예외를 밖으로 던지고 컨트롤러도 두 예외를 처리할 방법이 없기에 밖으로 던집니다. 웹 애플리케이션이라면 서블릿의 오류 페이지나, 또는 스프링 MVC가 제공하는 ControllerAdvice에서 이런 예외를 공통으로 처리합니다. API라면 보통 HTTP 상태코드 500(내부 서버 오류)을 사용해서 응답을 내려줍니다. 이렇게 해결이 불가능한 공통 예외는 별도의 오류 로그를 남기고 개발자가 오류를 인지할 수 있도록 메일, 알림(문자, 슬랙) 등을 통해 전달받아야 합니다.
2. 의존 관계에 대한 문제
서비스, 컨트롤러에서 java.sql.SQLException을 의존하고 있습니다. 향후 Repository를 JDBC > JPA 같은 기술로 변경하면 예외도 함께 변경해야 합니다. 그리고 해당 예외를 던지는 모든 다음 부분도 함께 변경해야 합니다.
logic() throws SQLException -> logic() throws JPAException
throws Exception
SQLException, ConnectException 같은 시스템 예외는 컨트롤러나 서비스에서는 대부분 복구가 불가능하고 처리할 수 없는 체크 예외입니다. 따라서 method() throws SQLException, ConnectException {..} 같이 처리해주어야 합니다. 그런데 다음과 같이 최상위 예외인 Exception을 던져도 문제를 해결할 수 있습니다. 그러나 Exception은 모든 체크 예외를 다 밖으로 던지는 문제가 발생합니다. 즉, 다른 체크 예외를 체크할 수 있는 기능이 무효화 되고, 중요한 체크 예외를 다 놓치게 됩니다. 따라서 꼭 필요한 경우가 아니면 Exception 자체를 밖으로 던지는 것은 좋지 않은 방법입니다.
void method() throws Exception {..}
체크 예외에 대한 문제점은 언체크 예외를 사용하여 해결할 수 있습니다.
언체크 예외
언체크 예외는 컴파일러가 예외를 체크하지 않는다는 뜻으로 RuntimeException과 그 하위 예외로 분류됩니다. 언체크 예외는 체크 예외와 기본적으로 동일하나 예외를 던지는 throws를 선언하지 않고 생략할 수 있습니다. 자동으로 예외를 던집니다.
/** * RuntimeException을 상속받은 예외는 언체크 예외가 된다. */ static class MyUncheckedException extends RuntimeException { public MyUncheckedException(String message) { super(message); } }
언체크 예외도 필요한 경우 잡아서 처리할 수 있습니다.
try { repository.call(); } catch (MyUncheckedException e) { //예외 처리 로직 log.info("error", e); }
언체크 예외는 체크 예외와 다르게 throws 예외를 선언하지 않아도 됩니다. 컴파일러가 해당 부분을 체크하지 않습니다. throws 예외를 선언한 경우, 컴파일러가 체크하지는 않으나 개발자가 이런 예외가 발생한다는 점을 IDE를 통해 좀 더 편리하게 인지할 수 있습니다.
public void callThrow() { repository.call(); }
위의 체크 예외를 언체크 예외로 바꾸면 아래와 같습니다. SQLException을 RuntimeSQLException으로, ConnectException을 RuntimeConnectException으로 바꾸었습니다. 런타임 예외이기에 서비스, 컨트롤러는 해당 예외를 신경쓰지 않아도 됩니다.
결론적으로 런타임 예외를 사용하면 서비스나 컨트롤러가 복구 불가능한 예외를 신경쓰지 않아도 되고, 체크 예외 처럼 예외를 강제로 의존하지 않아도 됩니다. 구현 기술이 변경되는 경우에도 공통 처리하는 한 곳만 변경하면 되기 때문에 변경의 영향 범위를 최소화할 수 있습니다. 이러한 이유로 최근 라이브러리들은 대부분 런타임 예외를 기본으로 제공합니다.
기본적으로 언체크(런타임) 예외를 사용하자. 체크 예외는 계좌 이체 실패, 로그인 ID, PW 불일치 등 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자.
예외 포함
예외를 전활할 때는 기존 예외를 포함해야 합니다.
public void call() { try { runSQL(); } catch (SQLException e) { throw new RuntimeSQLException(e); //기존 예외(e) 포함 } }
예외를 포함해야 기존에 발생한 java.sql.SQLException과 스택 트레이스를 확인할 수 있습니다.
[참고 정보]
반응형'BackEnd > Spring DB' 카테고리의 다른 글
JdbcTemplate (0) 2023.01.28 예외(Exception) (2) (0) 2023.01.28 트랜잭션(Transaction) (0) 2023.01.27 커넥션 풀(Connection Pool) & 데이터 소스(DataSource) (0) 2023.01.26 JDBC (0) 2023.01.25