4. 예외

예외

적절한 예외처리의 필요

1 초난감 예외처리

  • 예외블랙홀

    catch 선언하고 처리를안함... -> 에러가 나도 흘러가는거 처럼 보임

try{

} catch(SQLException e){
  // 아무처리를 안하면 로그가 안남고 정상처리 된 것 처럼 되기 떄문에 분석(디버깅)이 어려움
}
  • 로그만 찍는것은 예외처리라 할 수 없다

    검사예외 -> 1. 복구되거나, 2. 중단되거나 해야 함

// 그나마 나은 예오처리
try{

} catch(SQLException e){
  // 아무처리를 안하면 로그가 안남고 정상처리 된 것 처럼 되기 떄문에 분석(디버깅)이 어려움
  e.printStackTrace();
  System.exit(1); // <- 프로그램 중단, 그러나 이것도 좋은 코드는 아니다
}
  • throws 그냥 의미도 없이 의무적인 throws를 통한 예외 전파... 의미없어..

  • Error 시스템에서 발생한 비정상 처리 애플리케이션 코드에서 잡으면 안됨 ex) OutOfMemoryError, ThreadDeath 같은 에러를 어플리케이션 코드에서 잡아봐야 소용 없음

  • 검사예외 Execption에서 RuntimeException을 상속하지 않은 나머지

  • 비검사예외 1. Exception에서 RuntimeException을 상속하는 것들 2. catch로 안잡아도 된다. 잡아도 상관은 없다 3. 보통 프로그램 오류(ex, 비즈니스 로직 코드) 1. ex) NullPointerException

  • 검사/비검사예외 예

    // 비검사예외
    @Test
    public void test() {
      int a = 0;
      if (a < 0) {  // 런타임예외 : catch 블록을 잡을 필요가 업음
        throw new RuntimeException("a이 값이 0 이상이어야 함"); // 이자리에 NullPotinerException 등 런타임예외 클래스가 들어가면 됨
      }
      System.out.println("a = " + a);
    }
    
    // 검사예외
    @Test
    public void test4_2() {
      try {
        InputStream is = new FileInputStream(new File("test.txt"));
      } catch (FileNotFoundException e) {
        e.printStackTrace();  // 검사예외 같은 경우 catch로 얘외상황에 대한 적절한 처리가 필요하다. 즉, 예외발생시 추가적인 어떤 작업이 필요한 경우
      }
    }
  • 예외복구 무조건 복구하는게 아니라 적절히 복구-> or

  • 예외회피(throw) throws를 통해 다른쪽에 던지는건데.. 이처리를 할 수 있는데 까지 던져서 처리할수 있도록해야함. 그냥 막 throws하는건 아님.. 이게 회피

    DAO -> Service -> Controller 무작정 던지는건 ? 음 의미없다 회피도 복구처럼 분명한 어떤 기준이 필요함

  • 예외전환 예외를 던지는 것(회피)과 비슷하나 + 적절히 발생한 예외를 전환해서 전달

    public void test() throws DuplicationException, SQLException{
      try{
    
      } catch (SQLException e){
        if(dupl){
          throw new DuplicationException(); // 예외전환 (다른예외로 적절히 전환하여 처리)
        } else{
          throw e;
        }
      }
    }
    
    public void test() throws DuplicationException, SQLException{
      try{
    
      } catch (SQLException e){
        throw new DuplicationException(e); // 또는 throw DuplicationException().initCause(e)
      }
    }

복구하지 못할 예외라면... 검사예외더라도 비검사예외(런타임)로 랩핑(Wrap)해서 던저버리고, 예외처리 서비스 등을 이용해 자세한 로그를 남기고, 관리자에게는 메일로통보해주고, 사용자에게는 친절한 안내메시지를 보여주는 식으로 처리하는게 바람직.

대응(복구) 불가능한 검사예외는 비검사예외로 빨리 전환해서 처리하는게 낫다

요즘 오픈소스의 추세 : 비검사예외처리로 정의하는 case가 많음, 언제든지 catch 블록도 사용가능하고

catch 블록에는 예외상황에 대한 모든것을 기술, try에는 정상적인 코드 실행 흐름 예외에 대한 임의 리턴값은 상수화 or 정의된 코드를 표준화 하여 사용

  • 위에 검사/비검사예외 설명

    JdbcContext -> JdbcTemplate 변환했을 때, Exception이 사라진 이유 -> 즉, 비검사예외를 내부적으로 처리했거나, 내부적으로 검사예외를 처리했겠지, throw로 던지지 않고, DataAccessException으로 되어있다

스프링의 대부분 예외는 런타임예외로 구현되어 있다. -> 따로 검사처리를 하지 않아도 된다는 뜻이다. 굿!

2 예외전환

SQLException 래핑 -> DataAccessException이 처리

JDBC의 OOP 장점인 인터페이스 기반 개발 (Connection, Statement, ResultSet), 인터페이스를 제공하고 각 벤더사별로 구현

갑자기 SQL 표준/비표준 이야기...? -> 벤더사별로 SQL이 다르다, 따라서 이 SQL이 나중에 DB를 바꾸려고 할 때 걸림돌이 된다. -> SQLException : 예외 종류도 벤더사마다 다 제각각이라는 점...

  • getState() Open Group의 XOPEN SQL 스펙에 정의된 SQL 상태코드를 알 수 있음 (표준) DB연결실패 : 08S01, 테이블존재x : 42S02 등...

  • 예외케이스 (RuntimeException) SQL 문법오류 : BadSqlGrammerException DB 연결실패 : DataAccessResourceFailureException 데이터 제약조건 위배, 일관성 x : DataIntegrityViolationException 키중복 : DuplicatedKeyException DataAccessException의 하위

  • SQLErrorCodes 클래스 일부

    public class SQLErrorCodes {
    
      private String[] databaseProductNames;
    
      private boolean useSqlStateForTranslation = false;
    
      private String[] badSqlGrammarCodes = new String[0];
    
      private String[] invalidResultSetAccessCodes = new String[0];
    
      private String[] duplicateKeyCodes = new String[0];
    
      private String[] dataIntegrityViolationCodes = new String[0];
    
      private String[] permissionDeniedCodes = new String[0];
    
      private String[] dataAccessResourceFailureCodes = new String[0];
    
      private String[] transientDataAccessResourceCodes = new String[0];
    
      private String[] cannotAcquireLockCodes = new String[0];
    
      private String[] deadlockLoserCodes = new String[0];
    
      private String[] cannotSerializeTransactionCodes = new String[0];
    
      private CustomSQLErrorCodesTranslation[] customTranslations;
    
      private SQLExceptionTranslator customSqlExceptionTranslator;
    // ...
  • JdbcTemplate 예외 검사 예외 : SQLException 비검사 예외 : DataAccessExcepion -> Jdbc외 JPA, 하이버네이트, iBatis 등에서도 사용 됨

  • 인터페이스 구현 데이터 연동 기술 인터페이스가 다음코드 처럼 throws 형식이 다르다면... 다른 코드로 다형성을 이용할 수가 없다. 다행인건 Jdbc 기술보다 늦게나와서 런타임예외로 구현되어 있다. 따라서 내부적으로 예외처리를 하고 있는 셈이다. 왠만하면 검사예외 형식보다는 비검사예외로 구현하는것이 좋다 but, SQLException(검사예외) -> PersistentException, HibernateException, JdoException 식으로 되어 있어서 데이터 연동기술의 종속적이다. 즉, DAO(클라이언트)는 사용기술에 따라 검사예외를 달리 선언해야하는 경우가 발생 하게되므로 차후 변경이 발생할 경우, 검사예외 부분의 모든 소스를 고쳐야하는 불상사가 발생할 수 있다.

    // ...
    public void add(User user) throws PersistentException;  // JPA
    public void add(User user) throws HibernateException;   // Hibernate
    public void add(User user) throws JdoException;         // JDO

Looking 관련 예외 -> 스프링의 ObjectOptimisticLockingFaulureException을 사용하면 다른 기술도 상관없이 사용이 가능하다.(DataAccessException)

적절한 예외추상화 설계, 일관성 있는 예외를 설정 할 수 있도록 해줌.

  • 인터페이스 네이밍

    • 접두어 I를 붙이는 경우

      ex) IUserDao -> UserDao

    • 인터페이스는 단순화 시키고, 구현클래스는 각각의 특징을 따르는 방법

      ex) UserDao -> UserDaoJDbc, UserDaoJpa, UserDaoHibernate

    • 스프링 Bean id은 클래스이름이 아니라 인터페이스 이름을 따르는게 보통이다.

      기존의 타입 -> 상위타입으로 항상 유지해야한다. 그래야 나중에 변경시 의존되어 있는 곳에서 영향을 받지 않게 됨.

  • 예외변환

    SQLExceptionTranslator : 검사 -> 비검사로 전환

    SQLException -> DataAccessException 하위로 변환

  @Test
  public void test4_24() {
    try {
      User user = new User("1","so2","1234");
      userDao.add(user);
      userDao.add(user);
    } catch (DuplicateKeyException ex) {
      SQLException sqlEx = (SQLException) ex.getRootCause();  // 검사예외
      SQLExceptionTranslator set = new SQLErrorCodeSQLExceptionTranslator(this.dataSource); // 검사->비검사 변환
      DataAccessException translate = set.translate(null, null, sqlEx); // 비검사예외
      if (translate instanceof DuplicateKeyException) {
        System.out.println("ok"); // -> DuplicationKeyException으로 변환이 되었는지 확인
      } else {
        System.out.println("no");
      }
//      assertThat(set.translate(null, null, sqlEx), is(DuplicateKeyException.class.toString()));
    }
  }

Last updated