잘못된 예외 처리
-
예외를 잡고 아무것도 하지 않는 것
-
예외를 화면에 출력 -> 운영서버에 올라가면 찾기 힘들다.
-
시스템을 종료
예외의 종류와 특징
Error
- 시스템 레벨에서 발생.
- 애플리케이션에서 이런 에러에 대한 처리는 대응할수 없기 때문에 신경쓸 필요 없다.
Exception
1) Checked Exception
- 컴파일 단계에서 확일할 수 있는 예외.
- 컴파일 단계에서 처리해줘야한다.
- Checked Exception이 발생한 메서드에서 try catch로 잡거나
throws를 이용하여 메서드 밖으로 던져야한다.
2) Unchecked Exception
- 컴파일 단계에서 확인할 수 없는 예외.
- 컴파일 단계에서 처리를 반드시 할 필요는 없다.
예외처리 방법
[1] 예외 복구
- 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것.
- 예를 들면 사용자가 요청한 파일을 읽지 못햇을 때
사용자에게 상황을 알려주고 다른 파일을 이용하도록 유도한다. - 애플리케이션이 정상적으로 설계된 흐름을 따르도록 하는 것이다.
[2] 예외처리 회피
- 예외처리를 자신을 호출한 쪽으로 던지는 것.
- 예외를 회피하는 것은 의도가 명확해야한다.
자신을 사용하는 쪽에서 예외를 다루는게 최선일 때 회피해야한다.
[3] 예외전환
- 예외처리와 회피하게 메서드 밖으로 던지는 것이지만
발생한 예외를 그대로 넘기지 않고 적절한 예외로 전환하여 던진다.
- 크게 2가지 목적이 있다.
- (1) 의미를 분명하게 해줄 수 있는 예외로 전환.
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
} catch (SQLException e) {
if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
throw new DuplicateUserIdException(e).initCause(e);
} else {
throw e;
}
}
}
- (2) 처리하기 쉽도록 포장한다.
주로 예외처리를 강제하는 체크 예외를 런타임 예외로 바꿀 때 사용.
catch (SQLException e) {
...
throw new DuplicateUserIdException(e).initCause(e);
}
복구 가능한 예외 vs 복구 불가능한 예외
- 복구 가능한 경우는 체크 예외를 사용하여 적절한 대응을 한다.
try catch를 강제하기 때문이다.
- 복구 불가능한 경우는 런타임 예외를 사용하여 해당 메소드 밖으로 던진다.
해당 예외를 받은 메서드는 불필요하게 또다시 throws를 던질 필요가 없으며
그냥 예외를 발생시킨다.
서버환경에서의 예외 처리
- 대부분의 서버환경에서는 예외 처리를 일괄적으로 다룰 수 있는 기능을 제공한다.
- 복구하지 못할 예외라면 런타임 예외를 포장하여 던지고 예외처리 서비스를 이용하면 편리하다.
- 서버 환경에서는 쳬크 예외의 활용도가 떨어지고 있다.
예외처리 전략
체크 예외를 런타임 예외로 전환
//DuplicateUserIdException
public class DuplicateUserIdException extends RuntimeException {
public DuplicateUserIdException(Throwable cause) {
super(cause);
}
}
//add 메서드
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
} catch (SQLException e) {
if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
throw new DuplicateUserIdException(e);
} else {
throw new RuntimeException(e);
}
}
}
- add 메소드를 사용하는 오브젝트는 SQLException을 처리하기 위한 불필요한 throws를 선언할 필요가 없다.
- 런타임 예외를 일반화하면 장점이 많지만
컴파일 타임에 예외를 잡을 수 없기때문에 예외상황을 고려하지 못할 수도 있다.
스프링의 예외처리 전략
- 스프링의 JdbcTemplate는 위와 같은 전략을 따르고 있다.
- JdbcTemplate는 템플릿과 콜백 안에서 발생하는 SQLException을 런타임 예외인 DataAccessException을 포장해서 던진다.
- 따라서 JdbcTemplate을 사용하는 객체에서 DataAccessException을 처리해도 되고 처리를 안해도 된다.
SQLErrorCodeSQLExceptionTranslator.class에서 SQLException을 DataAccessException로 포장.
예외 전환
- 예외 전환은 2가지 목적을 갖고있다.
- [1] 위에서 SQLException -> DataAccessException으로 포장한 것처럼 런타임 예외로 바꾸는 용도.
- [2] 추상화된 예외로 변환.
추상화된 예외로 변환하여 얻는 효과는?
호환성 없는 SQLException의 DB 에러정보
- DB에 따라서 SQL뿐만 아니라 에러의 종류와 원인도 다르다.
- JDBC는 데이터 처리중에 발생하는 예외를 SQLException 하나에 모두 담고 던진다.
- 예외 발생 원인은 SQLException에 담긴 에러 코드와 상태정보를 참조해야하는데 이 역시 DB별로 다르다.
- DB에 독립적으로 에러 코드 처리를 하려면 어떻게 해야할까?
DB 에러 코드 매핑을 통한 전환
- DataAccessExcetpion은 SQLExcetpion을 단순히 런타임 예외로 대체할 뿐만 아니라 DataAccessExcetpion의 서브클래스로 세분화된 예외 클래스를 정의하고 있다.
- 스프링은 DB별 다른 에러 코드를 분류해서 DataAccessExcetpion의 서브클래스와 매핑한다.
아래는 MySQL 에러 코드 매핑 파일이다.(sql-error-codes.xml)
- 따라서 JdbcTemplate는 DB의 종류와 상관없이 동일한 예외를 받을 수 있다.
데이터 액세스 예외 추상화와 DataAccessException 구조
- 데이터 액세스 기술에는 JDBCTemplate(SQL Mapper)을 이용한 방식 이외에도 여러 방법이 존재한다.
- 예를 들면 MyBatis(SQL Mapper)나 JPA(ORM) 등이 있다.
ORM 방식
- 이런 경우 각 데이터 액세스 기술마다 예외가 다를 수 있다.
예를 들면 JPA와 같은 ORM에서는 발생하지만 JDBC에서는 없는 예외가 있다. - DataAccessException에서는 일부 기술에서만 발생할 수 있는 예외도 추상화하고 있다.