try/catch
로 감싸거나, throws
로 이를 호출한 메서드에게 예외를 넘겨준다.(이는 자바에만 존재하는 특별한 예외 처리 방식이다.)Checked Exception | Unchecked Exception | |
---|---|---|
처리 여부 | 반드시 예외 처리 해야함 | 예외 처리 하지 않아도됨 |
트랜잭션 Rollback 여부 | Rollback 안됨 (반드시 해결해야하므로 롤백하거나 처리하거나 선택지를 준다.) | Rollback 진행 (해결할 수 없으므로 롤백한다.) |
대표 Exception | IOException, SQLException | NullPointException, IllegalArgumentException |
기존에 예외를 처리할 때는 log를 통해 예외의 메시지만 출력해주고 아무것도 해주지 않았다. 그리고 Checked Exception을 커스텀 런타임 예외(Unchecked Exception)으로 변경하여 던져서 처리하는 경우에도 기존의 Checked Exception에 대한 정보를 보존하지 않고 메시지만을 처리하곤 했다. 이러한 경우 다음과 같은 문제가 발생한다고 생각했다.
그러면 내가 기존에 한 방식에서 문제점을 각각 살펴보고 해결방법을 알아보자.
private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);
public void update(String query) {
try {
// ...
} catch (SQLException e) {
log.error(e.getMessage());
}
}
로그 시스템을 사용하여 예외를 처리할 때 문제의 코드이다. 만약 이 코드에서 SQLException이 발생하면 아래와 같은 메시지가 출력된다.
위와 같은 출력 결과로서는 어디서 에러가 발생한 것인지 전혀 알 수 없다. 예외가 발생하는 경우 표시된 stack trace를 통해 예외를 추적해야 하는데, 위 그림에서는 이를 전혀 출력하고 있지 않다. stack trace는 다음과 같은 그림을 말한다.
이 외에도 다음과 같은 경우 exception을 보존할 수 없다고 한다.
try {
/* ... */
} catch (Exception e) { // Noncompliant - exception is lost
LOGGER.info("context");
}
try {
/* ... */
} catch (Exception e) { // Noncompliant - exception is lost (only message is preserved)
LOGGER.info(e.getMessage());
}
// Noncompliant - exception is lost
try { /* ... */ } catch (Exception e) { throw new RuntimeException("context"); }
아래와 같은 코드는 예외 처리를 하지 않은 것과 같다.
try {
// ...
} catch (Exception e) {
}
try {
// ...
} catch (Exception e) {
throw e;
}
위에서 stack trace는 예외가 발생한 부분을 추적할 수 있는 지표라고 했다. 그래서 아래와 같이 예외를 처리하는 경우가 있는데, 이보다는 로깅 프레임워크를 사용하는 습관을 들이는 것이 좋다.(활용할 수 있는 곳이 많다.)
try {
// ...
} catch (Exception e) {
e.printStackTrace();
}
예외가 발생했을 때 stack trace를 표시하고 exception 정보를 보존하는 방법은 기본적으로 다음과 같다.
try { /* ... */ } catch (Exception e) { LOGGER.info(e); }
그리고 내가 적용한 방법은 다음과 같다.
private static final Logger log = LoggerFactory.getLogger(JdbcTemplate.class);
public void update(String query) {
try {
// ...
} catch (SQLException e) {
log.error("Fail update :", e);
}
}
SQLException
을 매개변수로 넘겨주는 모습을 볼 수 있다. 위와 같이 사용하면 아래 그림과 같은 출력 결과를 볼 수 있다.
이제는 stack trace를 볼 수 있어 쉽게 어디서 예외가 발생했는지 알 수 있다.
예외를 처리할 때 로그를 활용하는 것 이외에 checked exception을 unchecked exception으로 변경해서 던져야 하는 경우가 있었다. 이를 자바에서는 RuntimeException 클래스를 상속받아서 할 수 있다. 이를 처리할 때도 기존의 예외를 보존해야 한다.
먼저, 기존의 문제 코드를 보자.
public void update(String query) {
try {
// ...
} catch (SQLException e) {
throw new DataAccessException();
}
}
위 코드는 checked exception인 SQLException
을 직접 구현한 런타임 예외 클래스인 DataAccessException
으로 바꿔서 던져주는 모습이다. 하지만 생성자로 아무것도 받지 않으므로 기존의 SQLException
정보를 보존할 수 없다. 위 코드에서 SQLException
이 발생하면 아래와 같은 결과가 나온다.
이를 해결하는 방법으로 RuntimeException
을 상속받아서 생성해 줄 때 Throwable
객체(RuntimeException
상위 클래스)를 생성자에 넣어줄 수 있다. 즉, catch한 exception을 생성자로 받아줄 수 있다.
public RuntimeException(String message, Throwable cause) {
super(message, cause);
}
물론 RuntimeException
을 생성할 때 Throwable
만을 받을 수 있는 생성자 등 여러 생성자를 제공해준다. 위 생성자를 사용한 이유는 따로 메시지를 출력해주고 싶었기 때문이다.
public void update(String query) {
try {
// ...
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
위와 같이 사용하면 직접 만든 메시지를 출력할 수 있고, 가장 마지막을 보면 SQLException이 어디서 발생했는지까지 표시해준다.