예외계층
•
Object : 예외도 객체이다. 모든 객체의 최상위 부모는 Object 이므로 예외의 최상위 부모도 Object
이다.
•
Throwable : 최상위 예외이다. 하위에 Exception 과 Error 가 있다.
•
Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외이다.
애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
◦
상위 예외를 catch 로 잡으면 그 하위 예외까지 함께 잡는다. 따라서 애플리케이션 로직에서는
Throwable 예외도 잡으면 안되는데, 앞서 이야기한 Error 예외도 함께 잡을 수 있기 때문에다.
애플리케이션 로직은 이런 이유로 Exception 부터 필요한 예외로 생각하고 잡으면 된다.
참고로 Error 도 언체크 예외이다.
•
Exception : 체크 예외 (Checked Exception)
◦
애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
◦
Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다.
◦
단 RuntimeException 은 예외로 한다.
•
RuntimeException : 언체크 예외, 런타임 예외 (Unchecked Exception)
◦
컴파일러가 체크 하지 않는 언체크 예외이다.
◦
RuntimeException 과 그 자식 예외는 모두 언체크 예외이다.
▪
RuntimeException 의 이름을 따라서 RuntimeException 과 그 하위 언체크 예외를 런타임
예외라고 많이 부른다.
예외에 대해서는 2가지 기본 규칙을 기억하자!
1.
예외는 잡아서 처리하거나 던져야한다.
2.
예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다.
Checked Exception
•
체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야한다! 그렇지 않으면 컴파일오류가 발생한다.
◦
처리: try~catch
◦
던지기: throws
•
Exception을 상속받은 예외는 체크예외가 된다.
Uncheced Exception
•
RuntimeException과 그 하위 예외
•
언체크예외는 말 그대로 컴파일러가 체크하지 않는 예외이다.
•
언체크예외는 체크예외와 기본적으로 동일하다. 차이가 있다면 예외를 던지는 thows를 선언하지 않고 생략할 수 있다. 이 경우 자동으로 예외를 던진다.
체크 예외 활용
기본 원칙
•
기본적으로 Unchecked Exception (Runtime Exception)을 사용하자.
•
체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자.
◦
해당 예외를 잡아서 반드시 처리해야하는 문제일 때
▪
계좌 이체 실패 예외
▪
결제시 포인트 부족 예외
▪
로그인 ID/PW 불일치 에외
중요한 예외인 경우 체크예외로 만들어 두면 컴파일러를 통해 놓친 예외를 인지할 수있다!
체크 예외의 문제점
•
체크예외는 컴파일러가 예외 누락을 체크해주기 때문에 개발자가 실수로 예외를 놓치는 것을 막아준다.
◦
명시적으로 예외를 잡아서 처리 || 예외를 던지도록 선언
•
기본적으로 체크예외를 사용하면 문제가 되는 이유?
1.
복구 불가능한 예외
•
대부분의 예외는 복구가 불가능하다.
•
SQLException 같은 경우 → 복구가 불가능 한 경우 → 서비스나 컨트롤러와 같은 비즈니스 레이어에서는 복구가 불가능하다. 이런 문제는 일관성있게 공통으로 처리해야한다. 오류 로그를 남기고 개발자가 오류를 빠르게 인지하도록.. (서블릿 필터/스프링인터셉터/스프링 ControllerAdvice)
2.
의존관계에 대한 문제
•
체크예외는 무조건 예외를 던지거나 처리해야하므로, 실제 예외처리가 불가능한 레이어에서도 예외처리를 해주어야하는 문제점 발생
•
Controller와 Service에서 SQLException을 의존하게 된다.
◦
SQLException은 저수준 모듈 (구현체), JDBC 기술에 종속된 예외이다.
◦
예를들어 JDBC에서 JPA로 기술을 변경하는 경우? SQLException에 의존하던 모든 서비스, 컨트롤러의 코드를 JPAException에 의존하도록 모두 변경해야한다.
▪
client코드의 변경없이 구현체를 변경한다고 하더라도 CheckedException을 처리해야하는 파급효과가 발생한다!!!
▪
CheckedException은 컴파일이 되지않기 때문에 변경범위가 팡팡
데이터베이스나 네트워크통신처럼 시스템 레벨에서 올라온 예외들은 복구가 불가능하고, 이런 경우에 체크예외를 사용하면 아래에서 올라온 복구 불가능한 예외를 서비스, 컨트롤러 같은 클래스가 모두 알고있어야 한다. → 불필요한 의존관계 문제 발생!
런타임 예외 구현 기술 변경시 파급효과
•
런타임 예외를 사용하면 중간에 구현체가 변경되어도 해당 예외를 사용하지 않는 컨트롤러, 서비스에서는 코드를 변경하지 않아도 된다.
•
예외를 공통으로 처리하는 곳에서만 예외를 변경해주면 되기 때문에 변경의 영향범위는 최소화된다!
•
대신! 런타임 예외는 문서화를 잘해야한다!
◦
특히 중요한 예외인 경우 throws 런타임예외를 명시해서 예외를 인지할 수 있도록 한다!
예외 포함과 스택트레이스
•
예외를 전환할때는 꼭!!!! 기존 예외를 포함해야한다.
void printEx() {
Controller controller = new Controller();
try {
controller.request();
} catch (Exception e) {
log.info("ex", e);
}
}
Java
복사
•
로그를 출력할 때, 마지막 파라미터에 예외를 넣어주면 로그에 스택트레이스를 출력할 수 있다.
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e); // 기존 예외 포함!
}
}
Java
복사
•
기존 예외를 포함 → cause 를 넘겨주면 기존 예외를 물고 갈 수 있다.
•
기존 예외를 포함하지 않은 경우? Caused By가 없다.. Root Cause를 알 수 없다!!!!
•
Transient: 일시적. 동일한 SQL을 반복해서 실행했을 때 성공할 가능성이 있다.
•
NonTransient: 동일한 SQL을 반복해서 실행하면 실패