4장에서는 JdbcTemplate 을 대표로 하는 스프링의 데이터 접근 기능에 담겨있는 예외처리와 관련된 접근 방법에 대해 알아본다.
JdbcTemplate은 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업(아래 나열)을 대신 처리해준다.
- 커넥션 획득
- statement 를 준비하고 실행
- 결과를 반복하도록 루프를 실행
- 커넥션 종료, statement , resultset 종료
- 트랜잭션 다루기 위한 커넥션 동기화
- 예외 발생시 스프링 예외 변환기 실행
- 개발자는 SQL을 작성하고, 전달할 파리미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
하지만 동적 SQL을 해결하기 어렵다는 단점이 있다.
직접 만든 JdbcContext 를 JdbcTemplate 으로 전환하는 과정에서 throws SQLException 선언이 사라졌음을 알 수 있다.
개발자들의 코드에서 종종 발견되는 초난간 예외처리의 예제를 알아보도록 하겠다.
예외 블랙홀
try {
...
} catch(Exception e) {
e.printStackTrace();
}
위 코드는 일반적으로 하는 예외 처리 코드 같아보이지만, 절대로 이와 같이 코드를 작성하면 안 된다.
예외 처리의 핵심 원칙
모든 예외는 적절하게 복구되든지, 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보되어야 한다.
이때, 특별하게 예외를 잡아서 조치할 방법이 없다면 잡지 말고 메서드 밖으로 던져서 책임을 전가해야한다.
무의미하고 무책임한 throws
public void method1 () throws Exception {
method2();
}
public void method2() throws Exception {
method3();
}
public void mehtod3() throws Exception {
...
}
위 코드는 모든 예외를 무조건 던져버리는 선언을 모든 메서드에 넣는 것 이다. 이런 코드 또한 무책임한 throws 선언이란 심각한 문제점이 있다.
위 두가지 예외 처리 방식은 어떤 경우에도 용납하지 않아야 한다.
Java 예외의 종류 - 출처
Error
Exception
RuntimeException (Unchecked Exception)
OtherException (Checked Exception)
예외 복구
예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것이다.
예외로 인해 기본 작업 흐름이 불가능하다면, 다른 작업 흐름으로 자연스럽게 유도해주는 것이다.
예시
- 파일이 읽어지지 않는 IOException
- 사용자에게 상황을 알려주고, 다른 파일을 이용하도록 안내한다.
- DB 서버에 접속이 실패하여, SQLException이 발생한 경우
- 일정시간 대기 후, 재시도해본다. 이때 정해진 횟수 만큼의 재시도가 실패할 경우 예외복구를 포기한다.
예외처리 회피
예외 처리를 자신이 담당하지 않고, 자신을 호출한 쪽으로 던져버리는 것이다.
throws 문으로 선언하여 예외가 발생하면 알아서 던져지게 하거나, catch 문으로 일단 예외를 잡은 후에 로그를 남기고 다시 예외를 던지는 방법이 있다.
예외를 회피하는 것 또한 의도가 분명해야 한다.
즉, 자신을 사용하는 (책임이 있는) 쪽에서 예외를 다루도록 하는게 최선의 방법이다.
예외 전환
예외 전환의 목표
어차피 복구하지 못할 예외라면
지금까지 살펴본 예외의 종류와 처리 방법 등을 기준으로 일괄된 예외처리 전략을 정리해보자.
런타임 예외의 보편화
애플리케이션 예외
시스템 또는 외부의 예외상황이 원이이 아니라, 애플리케이션 자체의 로직에 의해 의도적으로 발생 시키고, 반드시 catch 해서 조치를 취하도록 요구하는 예외이다.
예시) 은행계좌에서 요청한 금액을 출금하는 서비스 시, 잔고 부족 예외
예외 전환의 목적은 아래와 같다.
JDBC는 자바를 이용해 DB에 접근하는 방법을 추상화된 API 형태로 정의해놓고,
각 DB업체가 JDBC 표준을 따라 만들어진 드라이버를 제공하게 해준다.
즉, DB의 종류에 상관없이 일관된 방법으로 프로그램을 개발하게 해주는 도구 이다.
하지만 DB 종류에 상관없이 사용할 수 있는 데이터 접근 코드를 작성하는 일은 쉽지 않다.
현실적으로 2가지 걸림돌이 있다.
비표준 SQL
SQL은 어느정도 표준화된 언어이고 몇가지 표준 규약이 있다.
하지만, 개부분의 DB는 표준을 따르지 않는 비표준 문법과 기능을 제공한다.
비표준 특정 DB 전용 문법은 매우 폭넓게 사용되고 있다.
이를 해결할 수 있는 방법은 DAO를 DB별로 만들어 사용하거나 SQL을 외부에서 독립시켜서 바꿔 쓸 수 있게 하는 것이다.
호환성 없는 SQLException의 DB 에러 정보
DB마다 에러의 종류와 원인도 제각각이다.
그러므로 JDBC는 데이터 처리 중 발생하는 예외를 그냥 SQLException 하나에 모두 담아버린다.
예외가 발생한 원인은 SQLException 안에 담긴 에러 코드와 SQL 상태정보를 참조해봐야 알 수 있다.
하지만, 이마저도 DB 에러 코드가 DB 별로 모두 다르다.
SQL 상태정보는 DB 종류에 독립적으로 정보를 제공하므로, 이를 통해 어느정도 에러 처리가 가능하지만,
이 상태정보 또한 제대로 만들어지지 않는 경우가 대부분이다.
즉, 호환성 없는 SQLException의 정보를 활용하여 DB에 독립적인 유연한 코드를 작성하는 건 불가능에 가깝다.
결국 DB에 독립적인 DAO를 만들기 위해서는, 위 2가지 걸림돌을 해결해야 한다.
이번장에서는 2번째 문제인 호환성 없는 SQLException의 DB 에러 정보에 대한 해결책을 살펴보도록 하겠다.
해결 방법은 DB별 에러 코드를 참고하여 발생한 예외의 원인이 무엇인지 해석해주는 기능을 만드는 것 이다.
예제) 키 값이 중복될 경우 발생하는 에러 코드
- MySQL : 1062, Oracle : 1, DB2 : -803
스프링은 DB별 에러 코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑 정보 테이블을 만들어두고 이를 이용한다.
스프링이 정의한 예외 클래스란?
DataAccessException의 서브 클래스이다.
데이터 액세스 작업 중에 발생할 수 있는 예외 상황을 수십가지 예외로 분류하고 이를 추상화해 정의한 다양한 예외 클래스이다.
예시 : BadSqlGrammarException, DuplicatedKeyException, ...
자바에는 JDBC 외에도 데이터 액세스를 위한 표준 기술이 존재한다.
DataAccessException은 의미가 같은 예외라면, 데이터 액세스 기술의 종류와 상관없이 일관된 예외가 발생하도록 만들어준다.
즉, 데이터 액세스 기술에 독립적인 추상화된 예외를 제공하는 것이다.
스프링은 이와 같이 DataAccessException 계층구조를 이용해 데이터 액세스 기술에 독립적인 예외를 정의하고 사용한다.
스프링의 데이터 액세스 전략이나, DataAccessException의 예외 사용법은 11장에서 더 자세히 알아보도록 하겠다.