레시피 9-5 스프링 JDBC 프레임워크에서 예외 처리하기

umtuk·2022년 1월 25일
0

스프링 JDBC 프레임워크에서 예외 처리하기

과제

대부분의 JDBC API는 반드시 붙잡아 처리해야 하는 체크 예외 java.sql.SQLException을 던지도록 선언. 매번 DB 작업을 할 때마다 체크 예외를 처리하기는 매우 번거로워 자차적으로 처리 정책을 수립해 처리하지 않으면 일관성이 결여됨

해결책

JDBC 프레임워크를 비롯한 스프링 데이터 액세스 모듈은 일관성 있는 예외 처리 메커니즘을 제공
스프링 JDBC 프레임워크에서 발생한 모든 예외는 RuntimeException을 상속한 org.springframework.dao.DataAccessException의 하위 클래스라서 강제로 붙잡을 필요 없음
DataAccessException는 스프링 데이터 액세스 모듈이 던지는 최상위 예외 클래스

풀이

스프링 JDBC에서 예외를 처리하는 방법, 커스텀 예외 및 매핑을 사용해 활용하는 방법

스프링 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 계열의 예외를 특정짓는 기준
SQLExceptionerrorCode, 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 상태값메시지 텍스트
23505unique_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 템플릿에 주입

profile
https://github.com/umtuk

0개의 댓글