[Spring] 에러 코드로 예외 처리하기, 스프링 예외 추상화 사용하기

728x90

 

에러 코드로 예외 처리

예외 처리

발생하는 예외를 서비스 계층에서 복구하고 싶을 수도 있다.

 

예외의 오류 코드를 확인하여 체크 예외를 언체크 예외로 변환하여 서비스 계층으로 예외를 던지면,

서비스 계층에서 특정 기술에 의존하지 않고, 문제를 복구할 수 있다.

 

예시 코드

class Repository {
  private final DataSource dataSource;
  
  public Member save(Member member) {
    String sql = "insert into member(member_id, money) values(?, ?)";
    
    Connection con = null;
    PreparedStatement pstmt = null;
    
    try {
      con = dataSource.getConnection();
      pstmt = con.prepareStatement(sql);
      pstmt.setString(1, member.getMemberId());
      pstmt.setInt(2, member.getMoney());
      pstmt.executeUpdate();
      return member;
    } catch (SQLException e) {
      if (e.getErrorCode() == 23505) {
        throw new MyDuplicateKeyException(e);
      }
      throw new MyDbException(e);
    } finally {
      closeStatement(pstmt);
      closeConnection(con);
    }
  }
}

`MyDuplicateKeyException()` 과 `MyDbException()` 은 언체크 예외를 발생시키는 커스텀 예외이다.

 

class Service {
  private final Repository repository;
  
  public void create(String memberId) {
    try {
      repository.save(new Member(memberId, 0));
    } catch (MyDuplicateKeyException e) {
      String retryId = generateNewId(memberId);
      repository.save(new Member(retryId, 0));
    } catch (MyDbException e) {
      throw e;
    }
  }
}

 

문제점

- 데이터베이스마다 다른 오류 코드를 모두 다루기는 힘들다

 

따라서 스프링은 데이터 접근과 관련된 예외를 추상화하여 제공한다.

 

스프링 예외 추상화

DataAccessException

스프링이 데이터 접근 계층에 대한 일관된 예외 추상화를 제공한다.

RuntimeException 을 상속받았기 때문에, 모든 예외는 런타임 예외이다.

 

- 2가지 분류

`Transient` : 일시적인 예외로, 다시 시도하면 성공할 수도 있다. (ex. 쿼리 타임아웃, 락 관련 등)

`NonTransient` : 일시적이지 않은 예외로, 다시 시도해도 실패한다. (ex. SQL 문법 오류, DB 제약조건 위배 등)

 

따라서 서비스, 컨트롤러 계층에서 예외 처리가 필요해도, 특정 기술에 종속적이지 않게 사용할 수 있다.

 

SQLExceptionTranslator

스프링이 제공하는 SQL 예외 변환기이다.

(DataAccessException 을 상속받아 만들어졌다.)

 

`translate()`

  : 첫 번째 파라미터는 설명, 두 번째 파라미터는 실행한 sql, 세 번째 파라미터는 발생된 예외

  : 단순 예외를 스프링 데이터 접근 계층의 예외로 변환해서 반환해 준다.

 

sql-error-codes.xml

<bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
  <property name="badSqlGrammarCodes">
    <value>42000,42001,42101,42102,42111,42112,42121,42122,42132</value>
  </property>
  <property name="duplicateKeyCodes">
    <value>23001,23505</value>
  </property>
</bean>
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
  <property name="badSqlGrammarCodes">
    <value>1054,1064,1146</value>
  </property>
  <property name="duplicateKeyCodes">
    <value>1062</value>
  </property>
</bean>

이런 식으로 SQL 에러 코드를 어떤 스프링 데이터 접근 예외로 변환해야 할지 찾는다.

 

예시 코드

class Repository {
  private final DataSource dataSource;
  
  public Member save(Member member) {
    String sql = "insert into member(member_id, money) values(?, ?)";
    
    Connection con = null;
    PreparedStatement pstmt = null;
    
    try {
      con = dataSource.getConnection();
      pstmt = con.prepareStatement(sql);
      pstmt.setString(1, member.getMemberId());
      pstmt.setInt(2, member.getMoney());
      pstmt.executeUpdate();
      return member;
    } catch (SQLException e) {
      if (e.getErrorCode() == 23505) {
        throw exTranslator.translate("save", sql, e);
      }
      throw new MyDbException(e);
    } finally {
      closeStatement(pstmt);
      closeConnection(con);
    }
  }
}

`throw exTranslator.translate("save", sql, e)` 를 통해 스프링 데이터 접근 예외로 변환해 준다.

 

class Service {
  private final Repository repository;
  
  public void create(String memberId) {
    try {
      repository.save(new Member(memberId, 0));
    } catch (BadSqlGrammarException e) {
      String retryId = generateNewId(memberId);
      repository.save(new Member(retryId, 0));
    } catch (DataAccessException e) {
      throw e;
    }
  }
}

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 DB 1(김영한) - 인프런

 
728x90