

Object
Throwable
Error
Exception
RuntimeException
예외 기본 규칙
1. 예외는 잡아서 처리하거나 던져야 한다.
2. 예외를 잡거나 던진 때 지정한 예외 뿐만 아니라 해당 예외의 자식들도 함께 처리된다.
Checked Exception 장단점
장점 : 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다.
단점 : 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 너무 번거로운 일이 된다. 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다.
public class CheckedAppTest {
@Test
void checked() {
Controller controller = new Controller();
assertThatThrownBy(() -> controller.request())
.isInstanceOf(Exception.class);
}
static class Controller {
Service service = new Service();
public void request() throws SQLException, ConnectException {
service.logic();
}
}
static class Service {
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() throws SQLException, ConnectException {
repository.call();
networkClient.call();
}
}
static class NetworkClient {
public void call() throws ConnectException {
throw new ConnectException("연결 실패");
}
}
static class Repository {
public void call() throws SQLException {
throw new SQLException("ex");
}
}
}
Unchecked Exceptioin를 통해 이러한 문제를 해결해보자.
throws를 선언하지 않고 생략할 수 있고, 이 경우 자동으로 예외를 던짐Unchecked Exception 장단점
장점 : 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있다
단점 : 개발자가 실수로 예외를 누락할 수 있다.
public class UncheckedAppTest {
@Test
void unchecked() {
Controller controller = new Controller();
assertThatThrownBy(() -> controller.request())
.isInstanceOf(Exception.class);
}
static class Controller {
Service service = new Service();
public void request() {
service.logic();
}
}
static class Service {
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() {
repository.call();
networkClient.call();
}
}
static class NetworkClient {
public void call() {
throw new RuntimeConnectException("연결 실패");
}
}
static class Repository {
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e);
}
}
public void runSQL() throws SQLException {
throw new SQLException("ex");
}
}
static class RuntimeConnectException extends RuntimeException {
public RuntimeConnectException(String message) {
super(message);
}
}
static class RuntimeSQLException extends RuntimeException {
public RuntimeSQLException(Throwable cause) {
super(cause);
}
}
}
SQLException이 발생하면 런타임 예외인 RuntimeSQLException으로 전환해서 (스택 트레이스와 관련) 예외를 던진다.Filter, Interceptor, 서블릿 오류 페이지나 ControllerAdvice를 통해 공통으로 처리하면 된다.예외를 전환할 때는 꼭 기존 예외를 포함해야 한다. 그렇지 않으면 스택 트레이스를 확인할 때 심각한 문제가 발생한다.
<기존 예외를 포함하는 경우>
static class Repository {
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e); //기존 예외(e) 포함
}
}
public void runSQL() throws SQLException {
throw new SQLException("ex");
}
}

로그를 찍어서 확인해보면 RuntimeSQLException 예외를 포함해서 기존에 발생한 SQLException과 스택 트레이스를 확인할 수 있다.
<기존 예외를 포함하지 않는 경우>
static class Repository {
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(); //기존 예외(e) 제외
}
}
public void runSQL() throws SQLException {
throw new SQLException("ex");
}
}

로그를 찍어보면 기존에 발생한 java.sql.SQLException과 스택 트레이스를 확인할 수 없고 변환한 RuntimeSQLException 예외만 확인할 수 있다. 만약 실제 DB에 연동했다면 DB에서 발생한 예외를 확인할 수 없는 심각한 문제가 발생한다.