토비의 스프링을 읽으며 나중에 또 찾아볼 것 같은 내용을 정리하고자 한다. (개념 위주로 ~ 😀) 4장은 '예외'에 관련 내용이다.😀
try {
...
} catch (Exception e) {
//예외를 잡고 아무것도 하지 않는 것
}
오류가 있어서 예외가 발생했는 데, 그것을 무시하고 진행하게된다.
예상치 못한 다른 문제를 일으킬 것이다.
왜 exception 로그를 남길 때,
System.out
을 사용하지 않고logger
를 사용해야 할까 ?
성능상의 차이가 존재한다.
logger
와 달리System.out
은outputStream
을 사용하는데 바이트 단위로 읽기 때문에 성능적으로logger
보다 좋지 않다.
자신이 사용하려고 하는 메소드에 throws Exception
이 선언되어 있다고 생각해보자. 그런 메소드 선언에서는 의미 있는 정보를 얻을 수 없다. (무엇인가 실행 중에 예외적인 상황이 발생할 수 있다는 것인지, 습관적으로 복.붙을 한 것인지 알 수 없다.)
java.lang.Error
클래스의 서브 클래스 :: Errorjava.lang.RuntimeException
상속한 클래스 :: 언체크 예외catch
를 잡든지, 다시 throws
를 정의해서 던져야 한다. (하지 않는다면 컴파일 에러 발생)예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것
예외처리를 자신이 담당하지 않고, 자신을 호출한 쪽으로 던져버리는 것(throws
)
예외 처리 회피 시, 최선이라는 확신이 있어야 한다.
예외를 밖으로 던진다는 것은 예외 회피와 비슷하지만, 그대로 넘기지 않고 적절한 예외로 전환해서 던진다.
👉 로우 레벨 예외의 의미를 분명하게 하기 위해 (추상화 된 예외로 전환)
👉 예외 처리를 단순하게 하기 위해 포장 (체크 예외를 언체크 예외인 런타임 예외로 바꾸기 위해) > 굳이 필요하지 않은 catch/throws
를 줄여준다.
예외 전환 시, 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다.
어차피 복구하지 못할 예외라면 애플리케이션 코드에서 런타임 예외로 포장해서 던져버리고, 관리자에게 알리는 것이 바람직하다.
애플리케이션 로직상에서 예외조건이 발견되거나 예외상황이 발생한다면, 체크 예외를 사용하는 것이 적절하다. 비지니스적인 의미가 있는 예외는 이에 대한 적절한 대응이나 복구 작업이 필요하기 때문이다.
예외 포장 예시 - JdbcTemplate
SqlException
(체크 예외)를 DataAccessException
(언체크 예외)로 변환해주고 있다.
예외 추상화의 대표적인 예이다.
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
try {
// Create close-suppressing Connection proxy, also preparing returned Statements.
Connection conToUse = createConnectionProxy(con);
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("ConnectionCallback", sql, ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
스프링이 왜 이렇게 DataAccessException
(추상화된 예외)를 이용해 기술에 독립적인 예외를 정의하고 사용하게 하는지 생각해보자.
문제를 먼저 생각해보자! 데이터 엑세스 기술의 API를 변경한다고 정의해보자.
public void add(User user) throws SQLException; //Jdbc api
public void add(User user) throws PersistentException; //JPA
public void add(User user) throws HibernateException; //Hibernate
위와 같이 SQLException
을 던지도록 선언한 인터페이스 메소드는 데이터 엑세스 기술을 변경할 때 마다, 변경해주어야 한다는 문제가 발생한다.
즉, 데이터 엑세스 기술이 달라지면 같은 상황에서도 다른 종류의 예외가 던져진다는 점이 문제다. (데이터 엑세스 기술에 의존적인 코드가 된다.)
스프링은 자바의 다양한 데이터 엑세스 기술을 사용할 때 발생하는 예외들을 추상화해서 DataAccessException 계층 구조 안에 정리해놓았다. 따라서, 우리는 데이터 엑세스 기술과 구현 방법에 독립적으로 구현할 수 있다😀
참고로
DuplicateKeyException
은 아직까지 JDBC를 이용하는 경우에만 발생한다.SQLException
에 담긴 DB 에러 코드를 바로 해석하는 JDBC의 경우와 달리 JPA나 하이버네이트에서는 각 기술이 재정의한 예외를 가져와 스프링의 최종적으로DataAccessException
으로 변환하기 때문이다.