대부분의 JDBC API는 반드시 붙잡아 처리해야 하는 체크 예외 java.sql.SQLException을 던지도록 선언. 매번 DB 작업을 할 때마다 체크 예외를 처리하기는 매우 번거로워 자차적으로 처리 정책을 수립해 처리하지 않으면 일관성이 결여됨
JDBC 프레임워크를 비롯한 스프링 데이터 액세스 모듈은 일관성 있는 예외 처리 메커니즘을 제공
스프링 JDBC 프레임워크에서 발생한 모든 예외는 RuntimeException
을 상속한 org.springframework.dao.DataAccessException
의 하위 클래스라서 강제로 붙잡을 필요 없음
DataAccessException
는 스프링 데이터 액세스 모듈이 던지는 최상위 예외 클래스
스프링 JDBC에서 예외를 처리하는 방법, 커스텀 예외 및 매핑을 사용해 활용하는 방법
등록하려는 자동차 번호가 이미 등록
public class Main {
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(VehicleConfiguration.class);
VehicleDao vehicleDao = context.getBean(VehicleDao.class);
Vehicle vehicle = new Vehicle("EX0001", "Green", 4, 4);
vehicleDao.insert(vehicle);
}
}
main()
메서드를 두 번 연달아 실행하거나 번호가 EX0001인 자동차 레코드가 이미 DB에 있다면 DataAccessException
의 하위 클래스인 DuplicateKeyException
발생
DAO 메서드에서 try/catch 블록으로 감싸거나 메서드 시그니처에 throws를 덧붙일 필요 없음
DataAccessException
(및 그 하위 클래스 DuplicateKeyException
)은 언체크 예외라서 붙잡을 필요 없음
DataAccessException
의 직계 상위 클래스인 NestedRuntimeException
은 다른 예외를 RuntimeException
으로 감싸는 역할을 담당하는 스프링의 핵심 예외 클래스
스프링 JDBC 프레임워크는 SQLException
예외가 나면 자동으로 DataAccessException
의 하위 클래스 중 하나로 감쌈
이 예외는 붙잡아 처리하지 않아도 되는 RuntimeException
스프링 JDBC 프레임워크의 DataAccessException
계열의 예외를 특정짓는 기준
SQLException
의 errorCode
, SQLState
프로퍼티
DataAccessException
이 하부에 SQLException
을 최상위 예외 원인으로 감싼 모양새라 catch
블록에서 errorCode
, SQLState
프로퍼티 확인 가능
public class Main {
public static void main(String[] args) {
...
VehicleDao vehicleDao = context.getBean(VehicleDao.class);
Vehicle vehicle = new Vehicle("EX0001", "Green", 4, 4);
try {
vehicleDao.insert(vehicle);
} catch (DataAccessException e) {
SQLException sqle = (SQLException) e.getCause();
System.out.println("Error code: " + sqle.getErrorCode());
System.out.println("SQL state: " + sqle.getSQLState());
}
}
}
Error code: 0
SQL state: 23505
PostgreSQL 레퍼런스 메뉴얼의 에러 설명
SQL 상태값 | 메시지 텍스트 |
---|---|
23505 | unique_violation |
스프링 JDBC 프레임워크는 23505 상태값이 DuplicateKeyException
으로 매핑
org.springframework.jdbc.support
패키지에 위치한 sql-error-codes.xml
파일에 정의
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/?DTD BEAN 3.0//EN"
"http://www.springframework.org/dtd/spring-beans-3.0.dtd">
<beans>
...
<bean id="PostgreSQL" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="userSqlStateForTranslation">
<value>true</value>
</property>
<property name="badSqlGrammerCodes">
<value>03000,42000,42601,42602,42622,42804,42P01</value>
</property>
<property name="duplicateKeyCodes">
<value>23505</value>
</property>
<property name="dataIntegrityViolationCodes">
<value>23000,23502,23503,23514</value>
</property>
<property name="dataAccessResourceFailureCodes">
<value>53000,53100,53200,53300</value>
</property>
<property name="cannotAcquireLockCodes">
<value>55P03</value>
</property>
<property name="cannotSerializeTranscationCodes">
<value>40001</value>
</property>
<property name="deadlockLoserCodes">
<value>40P01</value>
</property>
</bean>
...
</beans>
userSqlStateForTranslation
은 에러 코드를 errorCode
대신 SQLState
프로퍼티로 매치
SQLErrorCodes
클래스에는 DB 에러 코드의 매핑 카테고리가 정의
스프링 JDBC 프레임워크에는 자주 나오는 에러 코드만 매핑되어 있어서 직접 매핑을 설정해야 하는 경우
기존 카테고리에서 코드를 하나 더 추가하거나 특정 에러 코드에 커스텀 예외를 정의해야할 때
MyDuplicateKeyException
커스텀 예외형으로 붙잡고 싶으면 데이터 무결성 위반 에러인 DataIntegrityViolationException
을 상속한 클래스 작성
스프링 JDBC 프레임워크가 던지는 모든 예외의 루트 클래스는 반드시 DataAccessException
public class MyDuplicateKeyException extends DataIntegrityViolationException {
public MyDuplicateKeyException(String msg) {
super(msg);
}
public MyDuplicateKeyException(String msg, Throwable cause) {
super(msg, cause);
}
}
기본적으로 스프링인 sql-error-codes.xml 파일에서 예외를 찾지만 클래스패스 루트에 이름이 같은 파일을 두고 오버라이드 가능
스프링은 제인 먼저 이 파일에서 예외를 찾아보고 적합한 예외가 없으면 기본 매핑을 탐색
커스텀 DuplicationException
형을 23505 에러 코드에 매핑하려면 CustomSQLErrorCodesTranslation
빈을 사용해 바인딩 추가, customTranslations
카테고리에 이 빈을 추가
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="PostgreSQL"
class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="useSqlStateForTranslation">
<value>true</value>
</property>
<property name="customTranslations">
<list>
<ref bean="myDuplicateKeyTranslation"/>
</list>
</property>
</bean>
<bean id="myDuplicateKeyTranslation"
class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation">
<property name="errorCodes">
<value>23505</value>
</property>
<property name="exceptionClass">
<value>
com.apress.springrecipes.vehicle.MyDuplicateKeyException
</value>
</property>
</bean>
</beans>
자동차 레코드 등록 코드를 감싼 try/catch 블록을 제거하고 중복된 자동차 레코드를 등록하면 스프링 JDBC 프레임워크에서 MyDuplicateKeyException
대신 발생
SQLErrorCodes
클래스의 기본 코드-예외 매핑 전략이 맘에 안들면 SQLExceptionTranslator
인터페이스를 구현하고 그 인스턴스를 setExceptionTranslator()
메서드를 사용해 JDBC 템플릿에 주입