-
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 상태코드를 믿고 결과를 파악하는 코드는 매우 위험
- 비표준 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를 만들 수 있음
- 스프링은 자바의 다양한 데이터 액세스 기술을 사용할 때 발생하는 예외들을 추상화 ~> DataAccessException 계층구조 안에 정리
기술에 독립적인 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 구현 내용에 관심을 가지고 테스트한다면, 해당 구현 클래스로 타입을 사용해서 테스트
- 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)
- 같은 정보를 두 명 이상의 사용자가 동시에 조회하고 순차적으로 업데이트할 때, 뒤늦게 업데이트한 것이 먼저 업데이트한 것을 덮어쓰지 않도록 막아주는 기능
반응형'Java & Spring > 토비의 스프링 3.1' 카테고리의 다른 글
5장) 5.2 트랜잭션 서비스 추상화 (0) 2021.06.26 5장) 5.1 사용자 레벨 관리 기능 추가 (0) 2021.06.26 4장) 4.1 예외 (0) 2021.06.17 3장) 3.6 스프링의 JdbcTemplate~ 3.7 정리 (0) 2021.06.17 3장) 3.4 컨텍스트와 DI ~ 3.5 템플릿과 콜백 (0) 2021.06.11