Spring

Spring JdbcTemplate에서는 SQLException를 어떻게 처리했을까?

1. 알고자 하는 것

- 일반적으로 jdbc를 사용해 쿼리를 수행하면 다음과 같이 Checked Exception인 SQLException이 필연적으로 throws 된다.

public void deleteAll() throws SQLException {
        Connection c = null;
        PreparedStatement ps = null;
        
        // getConnection, prepareStatement, executeUpdate 모두 SQLException을 throws 한다.
        c = dataSource.getConnection();
        ps = c.prepareStatement("delete from users");
        ps.executeUpdate();
        
        // 자원 정리(close) 코드는 편의상 생략.
    }

- 그러나 스프링에서 제공하는 JdbcTemplate을 사용해 쿼리를 수행하면 SQLException을 throws 하지 않는다.

public void deleteAllJdbcTemplate() { // throws SQLException이 없다.
    jdbcTemplate.update("delete from users");
}

- 스프링의 JdbcTemplate은 SQLException을 어떻게 처리하는지 알아본다.

- 또한, 왜 그러한 방식으로 처리하는지도 함께 알아본다.

 

2. 알게된 것

스프링의 JdbcTemplate은 SQLException을 어떻게 처리하는지

- 스프링의 JdbcTemplate 내부 코드를 들어가보면 모든 메서드가 다음과 같이 런타임 예외인 DataAccessException을 throws 한다.

public int update(final String sql) throws DataAccessException {
    ....
}

// DataAccessException은 RuntimeException을 상속받는다.
public abstract class DataAccessException extends NestedRuntimeException {
    public DataAccessException(String msg) {
        super(msg);
    }

    public DataAccessException(@Nullable String msg, @Nullable Throwable cause) {
        super(msg, cause);
    }
}

- update 메서드 내부에서 사용하는 execute 메서드를 보면 SQLException을 스프링 JdbcTemplate이 어떻게 처리하는지 보여준다.

@Nullable
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
    Statement stmt = null;

    Object var12;
    try {
        stmt = con.createStatement();
        this.applyStatementSettings(stmt);
        T result = action.doInStatement(stmt);
        this.handleWarnings(stmt);
        var12 = result;
    } catch (SQLException var10) {
        // try-catch 문을 통해 SQLException을 DataAccessException으로 전환한다.
        String sql = getSql(action);
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, this.getDataSource());
        con = null;
        throw this.translateException("StatementCallback", sql, var10);
    } finally {
        if (closeResources) {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }

    }

    return var12;
}

- translateException이라는 내부 메서드를 통해 SQLException을 DataAccessException으로 전환한다.

- translateException 메서드는 DB별 에러코드를 분류해 스프링이 정의한 예외 클래스와 매핑해놓은 에러코드 매핑정보 테이블을 활용해 발생한 에러 코드에 맞는 DataAccessException 계층구조 내 서브 클래스 중 하나로 매핑해 해당 예외를 throws한다.

 

왜 이러한 방식으로 SQLException을 처리하는지

1. 복구가 불가능한 Checked Exception을 가능한 빠르게 Runtime Exception으로 전환하기 위해

- SQLException은 대부분 프로그램 오류, 개발자 실수 등 코드레벨에서 복구가 불가능한 예외이다.

- 복구가 불가능한 예외를 밖으로 던져도 이를 처리할 수 있는 가능성이 없다.

- 따라서 기계적인 throws 선언을 해야하는 Checked Exception을 가능한 빨리 Runtime Exception으로 전환해야 한다.

- 스프링의 JdbcTemplate은 이러한 모든 SQLException을 런타임 예외인 DataAccessException으로 전환한다.

 

2. 로우레벨의 Exception을 의미있는 Exception으로 전환하기 위해

- jdbc는 자바를 통해 DB에 접근하는 방법을 추상화된 API 형태로 정의한다.

- 각 DB 벤더가 jdbc 표준에 해당하는 드라이버를 만들어 제공함으로써 DB 종류에 관계없이 일관된 방법으로 DB에 접근이 가능하다.

- 그러나 DB 종류에 관계없이 사용할 수 있는 DB 접근 코드를 작성하는 것은 완벽하고 유연할 수 없다.

- DB마다 독립적인 예외의 종류와 원인이 존재한다.

- 그래서 jdbc는 DB마다 독립적이고 제각각인 예외를 SQLException이라는 예외로 모두 담아 처리한다.

- 스프링의 JdbcTemplate은 이러한 로우 레벨의 SQLException에 대해 DB별 에러코드를 분류한다.

- 스프링이 정의한 예외 클래스와 매핑해놓은 에러코드 매핑정보 테이블을 활용해 발생한 에러 코드에 맞는 DataAccessException 계층구조 내 서브 클래스 중 하나로 매핑한다.

- ex) SQL 문법 오류 시 -> BadSqlGrammerException

- ex) DB 커넥션 가져오기 실패 -> DataAccessResourceFailureException

- 이를 통해 예외에 대해 명시적으로 예외의 종류를 구분할 수 있고, catch 문을 분기하여 독립적인 처리가 가능하다.

- 또한, 메서드를 사용하는 클래스에서 기술에 의존하는 Exception을 명시적으로 throws 하지 않을 수 있다.

- 즉, 데이터 액세스 기술과 구현방법에 독립적인 이상적인 DAO를 만들 수 있다.

 

3. 정리

- 스프링의 JdbcTemplate은 기존 jdbc의 Checked Exception인 SQLException을 Runtime Exception인 DataAccessException으로 예외를 전환하여 처리한다.

- 이는 복구가 불가능한 Checked Exception을 가능한 빠르게 Runtime Exception으로 전환하기 위해서이다.

- 또한, 다양한 DB의 예외를 하나의 SQLException으로 추상화 한 것을 DB 별 에러코드 매핑정보를 통해 명시적이고 일관된 DataAccessException 서브 클래스 예외로 매핑함으로써 명시적으로 예외를 구분하고 DB 기술에 독립적인 설계를 가능하게 한다.

 


Reference

토비의 스프링 3.1 Vol.1 - 4장 예외