예외 계층 그림
Throwable : 최상위 예외이다. 하위에 Exception 과 Error 가 있다.
Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외이다. 애 플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
상위 예외를 catch 로 잡으면 그 하위 예외까지 함께 잡는다. 따라서 애플리케이션 로직에서는 Throwable 예외도 잡으면 안되는데, 앞서 이야기한 Error 예외도 함께 잡을 수 있기 때문이다. 애 플리케이션 로직은 이런 이유로 Exception 부터 필요한 예외로 생각하고 잡으면 된다.
참고로 Error 도 언체크 예외이다.
Exception : 체크 예외
애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException 은 예외로 한다.
RuntimeException : 언체크 예외, 런타임 예외 컴파일러가 체크 하지 않는 언체크 예외이다.
RuntimeException 과 그 자식 예외는 모두 언체크 예외이다.
RuntimeException 의 이름을 따라서 RuntimeException 과 그 하위 언체크 예외를 런타임 예외라 고 많이 부른다. 여기서도 앞으로는 런타임 예외로 종종 부르겠다.
예외는 잡아서 처리하거나 던져야 한다.
예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다.
예를 들어서 Exception 을 catch 로 잡으면 그 하위 예외들도 모두 잡을 수 있다. 예를 들어서 Exception 을 throws 로 던지면 그 하위 예외들도 모두 던질 수 있다.
자바 main() 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료된다.
웹 애플리케이션의 경우 여러 사용자의 요청을 처리하기 때문에 하나의 예외 때문에 시스템이 종료되면 안 된다. WAS가 해당 예외를 받아서 처리하는데, 주로 사용자에게 개발자가 지정한, 오류 페이지를 보여준다.
테스트 던지면 바로 실패
예외가 일어난다
잡아서 정상적으로 처리하던가 예외를 터트리던가
MyCheckedException 는 Exception 을 상속받았다. Exception 을 상속받으면 체크 예외가 된다. 참고로 RuntimeException 을 상속받으면 언체크 예외가 된다. 이런 규칙은 자바 언어에서 문법으로 정한 것이다.
예외가 제공하는 여러가지 기본 기능이 있는데, 그 중에 오류 메시지를 보관하는 기능도 있다. 예제에서 보는 것 처럼 생성자를 통해서 해당 기능을 그대로 사용하면 편리하다.
Exception으로 잡아도 된다
catch 에 예외를 지정하면 해당 예외와 그 하위 타입 예외를 모두 잡아준다.
물론 정확하게 MyCheckedException 만 잡고 싶다면 catch 에 MyCheckedException 을 적어주어야 한다
장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다.
단점: 하지만 실제로는 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 너무 번 거로운 일이 된다. 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다. 추가로 의존관계에 따른 단점도 있 는데 이 부분은 뒤에서 설명하겠다.
서비스 입장에서 예외를 처리못하는데 모르겠다!
컨트롤 어드바이스 너가 처리해줬음좋겠다. 그런대 다 throw다 적어줘야되 내가 해결못하는데..
컨트롤 어드바이스까지 넘어가 체크예외를 전부만든다? 과해짐 어케 다만들어
전자 테스트 와 달라진점은 따로 선언을 하지 않아도 된다 메서드 옆에
언체크 예외는 주로 생략하지만, 중요한 예외의 경우 이렇게 선언해두면 해당 코드를 호출하는 개발자가 이 런 예외가 발생한다는 점을 IDE를 통해 좀 더 편리하게 인지할 수 있다.(컴파일 시점에 막을 수 있는 것은 아 니고, IDE를 통해서 인지할 수 있는 정도이다.)
언제 언체크(런타임) 예외를 사용하면 좋을까?
기본적으로 언체크(런타임) 예외를 사용하자.
체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자.
이 경우 해당 예외를 잡아서 반드시 처리해야 하는 문제일 때만 체크 예외를 사용해야 한다. 예를 들어 서 다음과 같은 경우가 있다.
체크 예외 예)
계좌 이체 실패 예외
결제시 포인트 부족 예외 로그인 ID, PW 불일치 예외
로직상 너무 중요해서 의도적으로 던지는경우에만 체크 예외 사용?
뭔가 반드시 놓치면 안돼 이럴 때 체크 예외 . 경험이 더쌓여야하나
서비스는 SQLException 과 ConnectException 를 처리할 수 없으므로 둘다 밖으로 던진다.
시스템 레벨 및에서 내려오는 에러들은 처리 방법이 없다
컨트롤러도 두 예외를 처리할 방법이 없다.
다음을 선언해서 예외를 밖으로 던진다.
웹 애플리케이션이라면 서블릿의 오류 페이지나, 또는 스프링 MVC가 제공하는 ControllerAdvice 에서 이런 예외를 공통으로 처리한다.
이런 문제들은 보통 사용자에게 어떤 문제가 발생했는지 자세히 설명하기가 어렵다. 그래서 사용자에 게는 "서비스에 문제가 있습니다." 라는 일반적인 메시지를 보여준다.
보통 500 에러
이렇게 해결이 불가능한 공통 예외는 별도의 오류 로그를 남기고, 개발자가 오류를 빨리 인지할 수 있 도록 메일, 알림(문자, 슬랙)등을 통해서 전달 받아야 한다.
체크 예외의 또 다른 심각한 문제는 예외에 대한 의존 관계 문제이다.
앞서 대부분의 예외는 복구 불가능한 예외라고 했다. 그런데 체크 예외이기 때문에 컨트롤러나 서비스 입장 에서는 본인이 처리할 수 없어도 어쩔 수 없이 throws를 통해 던지는 예외를 선언해야 한다.
서비스가 디비에 의존을 하게 된다. 이러면 다른 기술로 변경시 제약이 생김
리포지토리에서 체크 예외인 SQLException 이 발생하면 런타임 예외인 RuntimeSQLException 으로 전 환해서 예외를 던진다.
참고로 이때 기존 예외를 포함해주어야 예외 출력시 스택 트레이스에서 기존 예외도 함께 확인할 수 있
다.
시스템에서 발생한 예외는 대부분 복구 불가능 예외이다. 런타임 예외를 사용하면 서비스나 컨트롤러가 이 런 복구 불가능한 예외를 신경쓰지 않아도 된다. 물론 이렇게 복구 불가능한 예외는 일관성 있게 공통으로 처 리해야 한다.
/**
* Issue a single SQL execute, typically a DDL statement.
* @param sql static SQL to execute
* @throws DataAccessException if there is any problem
*/
void execute(String sql) throws DataAccessException;
뭐때문에 얘가 발생했지?
예외를 포함하지 않아서 기존에 발생한 java.sql.SQLException 과 스택 트레이스를 확인할 수 없다. 변 환한 RuntimeSQLException 부터 예외를 확인할 수 있다. 만약 실제 DB에 연동했다면 DB에서 발생한 예 외를 확인할 수 없는 심각한 문제가 발생한다.