Как JDBC использует исключение SQLException для публикации ошибок, так и Spring использует исключение DataAccessException с той же целью. Иерархия классов, начинающаяся с DataAccessExeption не привязана к JDBC и применяется в достаточно широком смысле внутри Spring для работы с данными. Преобразование из низкоуровневых классов исключений, характерных для конкретной реализации механизма работы с данными ( SQLExeption в нашем случае) производится с помощью механизма переводчиков исключений и, в контексте Spring JDBC, конкретно с помощью реализаций интерфейса SQLExceptionTranslator.
По умолчанию используется реализация SQLErrorCodeSQLExceptionTranslator, которая переводит исключения используя свои знания о конкретных базах данных и их кодах ошибок. Вместе со Spring JDBC распространяется файл sql-error-codes.xml c описаниями кодов ошибок, характерных для конкретных баз данных. Spring JDBC автоматически определяет базу, с которой он работает и использует списки ошибок от этой базы.
Если есть необходимость, можно расширить SQLErrorCodeSQLExceptionTranslator, добавить собственные ошибки или вмешаться в процесс обработки.
Допустим у нас в базе есть процедура, возвращающая наш собственный нестандартный SQLState:
1
2
3
4
5
6
|
CREATE OR REPLACE FUNCTION fail_me() RETURNS integer AS $f$
BEGIN
RAISE SQLSTATE 'SJ001';
RETURN 1;
END
$f$ LANGUAGE plpgsql;
|
И я бы хотел кидать особенное исключение, когда эта функция вызывается:
1
2
3
4
5
6
|
public class FailingFunctionCallException
extends NonTransientDataAccessException {
public FailingFunctionCallException(final String msg) {
super(msg);
}
}
|
Для этого я расширяю SQLErrorCodeSQLExceptionTranslator и переопределяю в нём метод customTranslate(), который должен либо вернуть какое-нибудь исключение, унаследованное от DataAccessException, либо null. В первом случае обработка сразу прекратиться, во втором управление будет передано механизму по умолчанию.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class CustomExceptionTranslator
extends SQLErrorCodeSQLExceptionTranslator {
@Override
protected final DataAccessException customTranslate(
final String task,
final String sql,
final SQLException ex) {
if (ex.getSQLState().equals("SJ001")) {
return new FailingFunctionCallException(
"Called function that intended to fail");
}
return null;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Test
public void testCustomException() {
exception.expect(FailingFunctionCallException.class);
exception.expectMessage(is("Called function that intended to fail"));
CustomExceptionTranslator cet = new CustomExceptionTranslator();
jdbcTemplate.setExceptionTranslator(cet);
jdbcTemplate.queryForObject("select fail_me()", Integer.class);
}
@Test
public void testDefaultException() {
exception.expect(BadSqlGrammarException.class);
exception.expectMessage(containsString("bad SQL grammar"));
CustomExceptionTranslator cet = new CustomExceptionTranslator();
jdbcTemplate.setExceptionTranslator(cet);
jdbcTemplate.queryForObject("select * from nonexistent", Integer.class);
}
|
Собственный транслятор исключений назначается всему экземпляру JdbcTemplate с помощью метода setExceptionTranslator(). У экземпляра JdbcTemplate может быть только один транслятор, поэтому если вы решите не расширять SQLErrorCodeSQLExceptionTranslator, а реализовывать SQLExceptionTranslator самостоятельно, вам придётся анализировать все исключения SQL и самостоятельно их всех транслировать.
Подключение к внешним базам данных.
Код из примера выше использует не встроенную базу данных, а внешнюю PostgreSQL базу. Поэтому XML описание контекста несколько другое. В частности для PostgreSQL:
1
2
3
4
5
6
|
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql://127.0.0.1/types"/>
<property name="username" value="types"/>
<property name="password" value="types"/>
</bean>
|
Для других баз данных идеологически описание DataSource остаётся тем же самым, но детали могут отличаться.
Код примера доступен на github. Код из примера использует PostgreSQL. В applicationContext.xml примера следует заменить адрес сервера с ‘127.0.0.1’ на адрес вашего PostgreSQL сервера, если он установлен не на локальной машине. Перед запуском необходимо так же исполнить скрипт schema.sql