ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4장) 4.2 예외 전환 ~ 4.3 정리
    Java & Spring/토비의 스프링 3.1 2021. 6. 21. 14:29
    반응형

    4장 예외

    4.2 예외 전환

    • 예외 전환의 목적

      • 런타임 예외로 포장
        • 불필요한 catch / throws 없애기
      • 의미있고 추상화된 예외로 바꿔 던지기
    • JdbcTemplate의 DataAccessException이 런타임 예외로 SQLException을 포장

      • 대부분 복구가 불가능한 예외인 SQLException을 Application 레벨에서 신경쓰지 않고, 상세한 예외 정보 전달의 목적
    • JDBC의 한계

      • DB 종류에 상관없이 사용할 수 있는 데이터 엑세스 코드를 작성하는 일이 쉽지 않음
      • 유연한 코드를 보장 못하는 두 가지
        • 비표준 SQL
          • 최적화 기법, 페이지 처리 등 비표준 SQL이 폭넓게 사용됨
          • 비표준 SQL은 DAO에 들어가게되고, 해당 DAO는 특정 DB에 종속적인 코드가 됨
          • 해결책
            • DAO를 DB별로 만들어 사용하거나 SQL을 외부에서 독립시켜서 DB에 따라 변경해 사용할 수 있도록 구성
        • 호환성 없는 SQLException의 DB 에러 정보
          • JDBC는 데이터 처리 중 발생하는 다양한 예외를 모두 SQLException 하나에 담음
          • SQLException에서 getErrorCode()가져오는 에러 코드는 DB별로 에러 코드가 다름
          • 그래서 SQLException은 DB 상태를 담은 SQL 상태정보를 부가적으로 제공하지만, JDBC 드라이버에서 SQLException을 담을 상태코드를 정확하게 만들어주지 않음
            • SQL 상태코드를 믿고 결과를 파악하는 코드는 매우 위험
    • DB 에러 코드 매핑을 통한 전환

      • 스프링은 DataAccessException 이라는 SQLException을 대체할 수 있는 런타임 예외 및 세분화된 다양한 예외 클래스를 제공

        • DataAccessResourceFailureException, BadSqlGrammaException 등
      • DB별 에러코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑정보를 만들고 이용

        • <bean id="Oracle" class="org.springframwork.jdbc.support.SQLErrorCodes">
              <property name="badSqlGrammarCodes">
                  <value>900,903,904,917,936,942,17706</value>
              </property>
              <property name="invalidResultSetAccessCodes">
                  <value>17703</value>
              </property>
              ...
          </bean>
          • 오라클 DB의 에러 코드 매핑 파일에 대한 예시
      • 위와 같이 매핑 정보를 참고해서 적절한 예외 클래스를 선택하기 때문에, DB가 달라져도 같은 종류의 에러라면 동일한 예외를 받을 수 있게 코드를 작성할 수 있음

      • 만약, 직접 정의한 예외를 발생시키고 싶다면, try-catch 블록을 이용해서 catch & throw로 자신이 정의한 예외로 던져주는 방법을 이용

      • JDK1.6, JDBC 4.0기준) SQLException의 서브 클래스들은 체크 예외기 때문에, 예외를 세분화하는 기준이 SQL 상태정보를 이용 하는 문제점이 존재

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

      • JDBC이외에 자바 데이터 엑세스 기술에서 발생하는 예외에도 적용
      • DAO 인터페이스와 구현 분리
        • 데이터 엑세스 로직과 다른 코드의 성격 분리 & 분리된 DAO는 전략 패턴을 적용해 구현 방법을 변경해서 사용 가능하게 구성
        • DAO의 사용 기술과 구현 코드는 전략 패턴과 DI를 통해 클라이언트에게 감출 수 있지만, 메소드 선언에 나타나는 예외정보가 문제
        • 인터페이스의 메소드 선언에 없는 예외를 구현 클래스에서 throws할 수 없음
          • 인터페이스에 throws를 추가
          • 하지만, JDBC가 아닌 다른 데이터 액세스 기술로 DAO를 구현하면 사용할 수 없는 코드
          • 가장 단순한 해결 방법은 throws Exception으로 선언하는 것이지만, 상당히 무책임함
          • 따라서, 런타임 예외를 이용해서 인터페이스에서 throws를 선언하지 않도록 해결
      • 데이터 액세스 예외 추상화와 DataAccessException 계층 구조
        • 스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화 ~> DataAccessException 계층구조 안에 정리
          • 공통적으로 나타나는 예외를 포함해서 데이터 액세스 기술에서 발생 가능한 대부분의 예외를 계층 구조로 분류
          • 템플릿 메소드나 DAO 메소드에서 직접 활용할 수 있는 예외도 정의되어 있음
            • 기대한 결과가 나오지 않은 예외상황에 대한 예외
              • IncorrectResultSizeDataAccessException, EmptyResultDataAccessException 등
        • 인터페이스 사용, 런타임 예외 전환과 함께 DataAccessException 예외 추상화를 적용하여, 데이터 액세스 기술과 구현 방법에 독립적인 DAO를 만들 수 있음
    • 기술에 독립적인 UserDao 만들기

      • 인터페이스 적용

        • UserDao에서 DAO 기능을 사용하려는 클라이언트들이 필요한 것만 추출해서 인터페이스로 구현

        • public interface UserDao {
              void add(User user);
              User get(String id);
              List<User> getAll();
              void deleteAll();
              int getCount();
          }
          • setDataSource() 메소드 같은 경우에는 구현 방법에 따라 변경될 수 있는 메소드이고, 클라이언트가 알고 있을 필요가 없기 때문에 제외
      • 테스트 보완

        • UserDao 인스턴스 변수를 구현 클래스로 바꿀 필요가 없음
          • @Autowired는 스프링 컨텍스트 내에서 정의된 빈 중 인스턴스 변수 주입이 가능한 타입의 빈을 찾아주기 때문
        • 특정 기술을 사용한 UserDao 구현 내용에 관심을 가지고 테스트한다면, 해당 구현 클래스로 타입을 사용해서 테스트
      • DataAccessException 테스트

        • @Test(expected=DataAccessException.class)
          public void duplicateKey() {
              dao.deleteAll();
          
              dao.add(user1);
              dao.add(user2); // 여기서 예외 발생
          }
      • DataAccessException 활용시 주의사항

        • 스프링을 활용하면 DB 종류와 데이터 액세스 기술에 상관없이 키 값이 중복되는 상황에서 동일한 예외가 발생하기로 기대

          • 하지만, DuplicateKeyException은 JDBC를 이용하는 경우에만 발생
        • DataAccessException이 기술에 상관없이 어느 정도 추상화된 공통 예외로 변환해주지만, 근본적인 한계 때문에 사용에 주의

        • 만약, 기술의 종류와 상관없이 동일한 예외를 얻고 싶다면, 직접 예외를 정의하고 상세한 예외 전환을 이용

          • DuplicateUserIdException이 예시
        • SQLException을 DataAccessException으로 전환하는 보편적인 방법은 DB 에러코드를 이용하는 방법

          • SQLErrorCodeSQLExceptionTranslator를 사용

            • 현재 연결된 DataSource를 필요로 함

            • @Test
              public void sqlExceptionTranslate() {
                  dao.deleteAll();
              
                  try {
                      dao.add(user1);
                      dao.add(user2);
                  } catch (DuplicateKeyException ex) {
                      SQLException sqlException = (SQLException)ex.getRootCause();
                      SQLExceptionTranslator set = new SQLErrorCodeSQLExceptionTranslator(this.datasource);
                      assertThat(set.translate(null, null, sqlException), is(DuplicateKeyException.class));
                  }
              }
          • JDBC 이외의 기술을 사용할 때, SQLException을 가져와서 직접 예외 전환하는 방법을 이용할 수 있음

        • SQLException을 그대로 두거나, 의미없는 RuntimeException으로 뭉뜽그려 던지는 대신 스프링의 DataAccessException 계층의 예외로 전환하는 방법을 염두하자

    4.3 정리

    • 엔터프라이즈 애플리케이션에서 사용할 수 있는 바람직한 처리 방법 학습
    • JDBC 예외의 단점과 스프링이 제공하는 효과적인 데이터 액세스 기술의 예외처리 전략과 기능 학습
      • 예외를 잡아서 아무런 조치를 취하지 않거나 의미없는 throws 선언을 남발하는 것은 위험
      • 예외는 복구하거나 의도적으로 전달하거나 적절한 예외로 전환해야함
      • 예외 전환
        • 의미있는 예외로 변경
        • 런타임 예외로 포장
      • 복구할 수 없는 예외는 런타임 예외로 빠르게 전환
        • SQLException이 대표 예시
      • 애플리케이션 로직 예외는 체크 예외
      • SQLException의 에러 코드는 DB에 종속 ~> 독립적인 예외로 전환할 필요 존재
      • 스프링은 DataAccessException이라는 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층 제공
      • DAO를 데이터 액세스 기술에서 독립
        • 인터페이스 도입
        • 런타임 예외 전환
        • 기술에 독립적인 추상화된 예외로 전환
    • 낙관적인 락킹(Optimistic Locking)
      • 같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트할 때, 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는 기능
    반응형

    댓글

Designed by Tistory.