< 하면 안되는 예외처리 >


1. 예외를 잡고 아무것도 하지 않는 코드



}catch(SQLException e) {
}
cs


2. 화면에 출력만 해주는 코드










// 1
}catch(SQLException e) {
    System.out.println(e);
}
 
// 2
}catch(SQLException e) {
    e.printStackTrace();
}
cs



예외는 처리되어야 한다.

모든 예외는 적절하게 복구되던지 작업을 중단시키고 분명하게 통보되야 한다.


굳이 예외를 잡아서 조치를 취할 방법이 없다면 잡지 말아야 한다.

메서드에 throws SQLException을 선언해서 메서드 밖으로 던지고 자신을 호출한 코드에 예외처리 책임을 전가하라.



< 무의미하고 무책임한 throws >

- 자신이 사용하려고 하는 메서드에 throws Exception이 있다면

  예외적인 상황이 발생할 수 있다는 것인지, 습관적으로 복붙한것이지 알수 없 다.



< 예외의 종류와 특징 >


Error

- java.lang.Error 클래스의 서브클래스

- 시스템에 뭔가 비정상적인 상황이 발생했을 때 사용

- 자바 VM에서 발생시키는 것이고 코드에서 잡으려고 하면 안된다. 

- OutOfMemoryError , ThreadDeath같은 에러는 catch로 잡아봐야 대응방법이 없다.


Exception

1. 체크 예외

- RuntimeException 을 상속하지 않은 클래스들

- 반드시 예외처리하는 코드를 함께 작성

ex) IOException, SQLException



2. 언체크 예외 ( 런타임 예외 )

- RuntimeException 을 상속한 클래스들

- 개발자가 부주의해서 발생할 수 있는 프로그램의 오류


ex) 

NullPointerException          : 오브젝트를 할당하지 않는 레퍼런스 변수를 사용하려고 했을때 

IllegalArgumentException    :허용되지 않는 값을 사용해서 메서드를 호출



< 예외 처리 방법 >


1. 예외복구

- 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것


ex1)

- 파일을 읽을 때 IOException이 발생

  사용자에게 알려주고 다른 파일을 이용하도록 안내해서 해결하는 방법이 있다.

  IOException 에러 메세지를 사용자에게 그냥 던지는 건 예외복구가 아니다.


ex2)

- DB서버에 접속하다 실패해서 SQLException이 발생

  일정시간 대기했다가 다시 접속을 시도해보는 방법이 있다.

  정해진 횟수만큼 재시도해서 실패했다면 예외 복구는 포기해야 한다.


2. 예외처리 회피

- 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 패스


ex1)


public void add() throws SQLException() { ... }
cs

- throws문으로 예외가 발생하면 알아서 던져지가 하거나

  catch문으로 예외를 잡은 후 로그를 남기고 다시 예외를 던지는 것(retrhow)


ex2)









public void add() throws SQLException() {
    try{
            ...
    } catch(SQLException e) {
        // 로그 출력
        throw e;
    }
}
cs


- 의도가 분명해야 한다. 

  콜백/템플릿처럼 긴밀한 관계에 있는 다른 오브젝트에게 예외처리 책임을 분명히 지게 하거나, 

  자신을 사용하는쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신이 있어야 한다.



3. 예외 전환

- 예외를 메서드 밖으로 던지지만 적절한 예외로 전환해서 던짐


목적

1. 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해


ex)

아이디 중복 오류면 DAO에서 SQLExceptionDuplicateUserIdException 으로 바꿔서 던짐


1
2
3
4
5
6
7
8
9
10
11
12
13
public void add(User user) throws DuplicateUserIdException, SQLException {
    try {
            ...
    } catch(SQLException e)  {
        //ErrorCode가 MySQL의 "Duplicte Entry(1062)" 이면 예외 전환
 
        if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)    {
            throw DuplicateUserIdException();
        
else {
            throw e;    // 그 외는 SQLException
        }
    }
}
cs



2. 예외를 처리하기 쉽고 단순하게 만들기 위해 포장(wrap)

- 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용

- 복구가 불가능한 예외라면 가능한 빨리 런타임 예외로 포장해 던지게 해서 

  다른 계층의 메서드를 작성할 때 불필요한 throws 선언이 들어가지 않도록 해야한다.


DAO에서 발생한 SQLException이 웹 컨트롤러까지 throws 된다고 해결이 안된다.

- EJbException은 RuntimeException클래스를 상속한 런타임 예외




catch (SQLException e) {
    throw new EJBException(e);
}
cs


즉, 런타임 예외로 포장해서 굳이 필요하지 않는 catch/throws를 줄여주는 것

로우레벨의 예외를 좀 더 의미있고 추상화된 예외로 바꿔서 던져주는 것


<SQLException>

- 대부분의 SQLException은 복구가 불가능

ex) SQL문법 오류, 제약조건 위배, DB서버 다운, 네트워크불안정, DB커넥션풀 초과 등등


- 필요없는 throws 선언을 하지 말고 빨리 언체크/런타임 예외로 전환해야함

- 스프링의 jdbcTemplate 템플릿과 콜백 안에서 발생하는 모든 SQLException을 

  런타임 예외인 DataAccessException으로 포장해서 던져준다.

  따라서 jdbcTemplate을 사용하는 메서드에선 꼭 필요한 경우에만 

  런타임예외인 DataAccessException을 잡아서 처리하면 되고 그외에는 무시해도 된다.


ex)


public int update(final Strng sql) throws DataAccessException {...}
cs



< 예외전환 >

- 런타임예외로 포장해서 굳이 필요하지 않은 catch/throws를 줄이는 것

- 로우레벨의 예외를 좀 더 의미있고 추상화된 예외로 바꿔서 던져주는 것

JDBC의 한계

1. 비표준 SQL

- 비표준 SQL은 DAO에 들어가고, 해당 DAO는 DB에 대해 종속적인 코드가 된다.

  DB의 변경 가능성을 고려해서 유연하게 만들어야 한다면 큰 걸림돌.


2. 호환성없는 SQLException의 DB 에러정보

- DB마다 에러의 종류와 원인도 제각각

- SQLException의 getErrorCode()로 가져올 수 있는 DB 에러코드도 제각각



< DB 에러 코드 매핑을 통한 전환 >

- 해결방법은 DB별 에러 코드를 참고해서 발생한 예외의 원인이 무엇인지 해석해주는 기능을 만드는 것

- DB종류에 상관없이 동일한 상황에서 일관된 예외를 전달받는다면 효과적인 대응이 가능하다


- 스프링은 DataAccessException 이라는 SQLException을 대체할 수 있는 런타임 예외를 정의하고, 

  DataAccessException의 서브클래스로 세분화된 예외 클래스들을 정의하고 있다

BadSqlGrammarException                : SQL 문법 오류

DataAccessResourceFailureException : DB 커넥션 오류

DataIntegrityViolationException        : 데이터 제약조건위배나 일관성을 지키지 않는 작업을 수행

DuplicatedKeyException                  : 중복 키 발생


그 외에도 수십가지 더 있다고 함


- 제각각인 에러코드는 DB별로 에러 코드를 분류해서 매핑파일을 만든다


ex) 



- 스프링의 에러코드 매핑을 통한 DataAccessException방식을 사용하는 것이 이상적



< DAO 인터페이스와 DataAccessException 계층구조 >

- 중복키 에러 발생시 데이터 액세스 기술의 API는 자신만의 독자적인 예외를 던진다..

JDBC             : SQLException

JPA               :PersistenceException

하이버네이트  : HibernateException


- DAO를 사용하는 클라이언트 입장에서는 DAO의 사용기술에 따라서 예외처리 방법이 달라져야 한다.


- 그래서 스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화해서 

 DataAccessException 계층구조 안에 정리해 놓았다


전략이나 DataAccessException의 예외 사용법은 11장에서 더 자세히...


여튼 결국 인터페이스 사용, 런타임 예외 전환과 함께 DataAccessException 예외 추상화를 적용하면 

데이터 액세스 기술과 구현방법에 독립적인 이상적인 DAO를 만들수 있다.




< 기술에 독립적인 UserDao 만들기 > 


1. UserDao 인터페이스

// setDataSource() 메서드는 인터페이스에 있으면 안된다.

1
2
3
4
5
public interface UserDao {
    void add(User user);
    User get(String id);
    ...
}
cs




2. UserDaoJdbc 클래스는 UserDao인터페스를 구현

1
public class UserDaoJdbc implements UserDao { .. }        
cs


3. 빈 클래스 변경

1
2
3
<bean id ="userDao" class="springbook.dao.UserDaoJdbc">
    <property name="dataSource" ref="dataSource" />
</bean>
cs




< 정리 >

- 예외를 잡아서 아무런 조치를취하지 않거나 의미 없는 throws 선언을 남발하는 것은 위험하다.

- 예외는 복구하거나, 예외처리 오브젝트로 의도적으로 전달하거나 적절한 예외로 전환해야 한다.

- 예외전환 방법

1. 좀 더 의미 있는 예외로 전환

2. 불필요한 catch/throws를 피학 위해 런타임 예외로 포장

- 복구할 수 없는 예외는 빨리 런타임예외로 전환

- 애플리케이션의 로직을 담기 위한 예외는 체크예외로 만든다.

- JDBC의 SQLException은 대부분 복구할 수 없는 예외이므로 런타임 예외로 포장해야 한다.

- SQLException의 에러 코드는 DB에 종속되기 때문에 DB에 독립적인 예외로 전환될 필요가 있다.

- 스프링은 DataAccessException을 통해 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층을 제공한다.

- DAO를 테이버 액세스 기술에서 독립시키려면 인터페이스 도임과 런타임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.

+ Recent posts