예외 전환의 목적
예외 전환의 목적은 두 가지이다.
- 런타임 예외로 바꿈으로써 필요하지 않은 catch문을 줄여주기 위함
- 로우레벨 예외를 좀 더 의미 있고 추상화된 예외로 바꾸기 위함
JDBC의 한계
JDBC는 자바를 이용해 DB에 접근하는 방법을 추상화된 API 형태로 정의해놓고, 각 DB 업체가 JDBC 표준을 따라 만들어진 드라이버를 제공하게 해준다. 하지만 DB를 자유롭게 변경해서 사용할 수 있는 유연한 코드를 보장해주지는 못한다. 이유는 다음과 같다.
비표준 SQL
많은 DB가 표준을 따르지 않는 비표준 문법과 기능을 제공한다. 비표준 SQL은 DAO 코드에 들어가고, 해당 DAO는 특정 DB에 종속적인 코드가 되어 버린다. 이를 해결하기 위한 방법은 두 가지이다.
- 호환 가능한 표준 SQL만 사용한다.
- 웹 프로그램에서 자주 사용되는 페이징 쿼리에서부터 문제가 되기 때문에 현실성이 없다.
- DB별로 별도의 DAO를 만들거나 SQL을 외부에 독립시켜서 DB에 따라 변경해 사용한다.
호환성 없는 SQLException의 DB 에러정보
DB마다 에러의 종류와 원인이 제각각이기 때문에 JDBC는 데이터 처리 중에 발생하는 예외를 SQLException 하나에 모두 담아버린다. SQLException의 getErrorCode()와 getSQLState() 메소드로 예외상황에 대한 상태정보를 가져올 수 있다. 하지만 상태정보가 표준스펙과 다르게 정의된 경우가 많기 때문에 SQLException만으로 DB에 독립적인 유연한 코드를 작성하는 건 불가능하다.
DB 에러 코드 매핑을 통한 전환
SQLException의 비표준 에러 코드와 상태정보에 대한 문제를 해결하기 위해 DB별 에러 코드를 참고해서 발생한 예외의 원인이 무엇인지 해석해주는 기능을 만든다. 키 값이 중복돼서 중복 오류가 발생하는 경우에 MySQL은 1062, 오라클이라면 1이라는 에러 코드를 받게 된다. 이런 에러 코드 값을 확인할 수 있다면 키 중복 때문에 발생하는 예외를 DuplicateKeyException이라는 예외로 전환할 수 있다.
문제는 DB마다 에러 코드가 제각각이라는 점이다. 스프링은 DB별 에러 코드를 분류해서 스프링이 정의한 예외 클래스와 매핑해놓은 에러 코드 매핑정보 테이블을 만들어두고 이를 이용한다.
<bean id="Oracle" class=org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>900,903,904</value>
</property>
<property name="InvalidResultSetAccessCodes">
<value>17003</value>
</property>
</bean>
JDBCTemplate은 DB의 에러 코드를 DataAccessException 계층구조의 클래스 중 하나로 매핑해준다. 드라이버나 DB 메타정보를 참고해서 DB 종류를 확인하고 DB별로 미리 준비된 매핑 테이블을 이용해 예외를 던진다. 따라서 DB가 달라져도 같은 종류의 에러라면 동일한 예외를 받을 수 있다.
DAO 인터페이스와 DataAccessException 계층구조
자바에는 JDBC 외에도 데이터 엑세스를 위한 표준 기술이 존재한다( JDO, JPA, iBatis 등 ). DataAccessException은 의미가 같은 예외라면 데이터 엑세스 기술의 종률와 상관없이 일관된 예외가 발생하도록 만들어준다.
DAO 인터페이스와 구현의 분리
DAO를 따로 만들어서 사용하면 데이터 엑세스 로직을 담은 코드를 성격이 다른 코드와 분리시킬 수 있다. 이렇게 분리된 DAO는 전략 패턴을 적용해 구현 방법을 변경해서 사용할 수 있게 만들 수도 있다. 이런 측면에서 DAO는 인터페이스를 사용해 구체적인 클래스 정보와 구현 방법은 감추고, DI를 통해 제공되도록 만드는 것이 바람직하다. 하지만 메소드 선언에 나타나는 예외정보가 문제가 될 수 있다. SQLException에러를 포함하고 있기 때문에 다음과 같이 만드는 것이 불가능하다.
public interface UserDao {
public void add(User user) throws SQLException;
public void add(User user); // 이렇게 작성할 수 없다.
}
이렇게 메소드 선언에 예외정보가 나타나면 JDBC가 아닌 다른 데이터 엑세스 기술로 전환하면 사용할 수 없다. DAO 인터페이스를 기술에 완전히 독립적으로 만들려면 예외가 일치하지 않는 문제를 해결해야 한다.
public void add(User user) throws PersistentException; //JPA
public void add(User user) throws HibernateException; //Hibernate
모든 예외를 다 받아주는 throws Exception으로 선언할 수 있다. 하지만 같은 예외 상황에 대해서도 다른 종류의 예외가 던져지기 때문에 문제가 된다. 즉, DAO를 사용하는 클라이언트 입장에서는 DAO의 사용 기술에 따라서 예외 처리 방법이 달라진다.
데이터 엑세스 예외 추상화와 DataAccessException 계층구조
스프링은 자바의 다양한 데이터 엑세스 기술을 사용할 때 발생하는 예외들을 추상화해서 DataAccessException 계층구조 안에 정리해놓았다. 스프링의 JDBCTemplate은 SQLException의 에러 코드를 DB 별로 매핑해서 그에 해당하는 서브클래스 중 하나로 전환해서 던져준다. 즉, JDBCTemplate과 같이 스프링 데이터 엑세스 지원 기술을 이용해 DAO를 만들면 사용 기술에 독립적인 일관성 있는 예외를 던질 수 있다.
기술에 독립적인 UserDao 만들기
인터페이스 적용
public interface UserDao {
void add(User user);
User get(String id);
List<User> getAll()
void deleteAll();
int getCount();
}
public 접근자를 가진 메소드이긴 하지만 UserDao의 setDataSource() 메소드는 인터페이스에 추가하면 안 된다. UserDao의 구현 방법에 따라 변경될 수 있는 메소드이고, UserDao를 사용하는 클라이언트가 알고 있을 필요도 없다.