JdbcTemplate – это центральный класс в основном пакете JDBC. Он обрабатывает создание и освобождение ресурсов, что помогает избежать распространенных ошибок, например, если забыть закрыть соединение. Он выполняет базовые задачи основного рабочего процесса JDBC (такие как создание и выполнение инструкций), оставляя код приложения, чтобы предоставлять SQL и извлекать результаты. Класс JdbcTemplate:

  • Выполняет SQL-запросы

  • Обновляет стейтменты и вызовы хранимых процедур

  • Выполняет итерацию экземпляров ResultSet и извлечение возвращаемых значений параметров.

  • Перехватывает исключения JDBC и преобразовывает их в типизированную, более информативную иерархию исключений, определенную в пакете org.springframework.dao. (См. раздел Согласованная иерархия исключений).

Если вы используете шаблон шаблон JdbcTemplate для своего кода, нужно лишь реализовать интерфейсы обратного вызова, предоставив им четко определенный контракт. Учитывая Connection, предоставленное классом JdbcTemplate, интерфейс обратного вызова PreparedStatementCreator создает скомпилированный стейтмент, предоставляя SQL и все необходимые параметры. То же самое справедливо и для интерфейса CallableStatementCreator, который создает вызываемые инструкции. Интерфейс RowCallbackHandler извлекает значения из каждой строки ResultSet.

Можно использовать JdbcTemplate внутри реализации DAO путем прямого создания экземпляра со ссылкой на DataSource, или же можно сконфигурировать его в IoC-контейнере Spring и передать его DAO в качестве ссылки на бин.

DataSource всегда должен конфигурироваться в виде бина в IoC-контейнере Spring. В первом случае бин передается службе напрямую; во втором случае он передается подготовленному шаблону.

Весь SQL, выдаваемый этим классом, регистрируется на уровне DEBUG в категории, соответствующей полному имени класса экземпляра шаблона (обычно JdbcTemplate, но оно может быть и другим, если вы используете пользовательский подкласс класса JdbcTemplate)

В следующих разделах приведены некоторые примеры использования JdbcTemplate. Эти примеры не являются исчерпывающим списком всех функциональных возможностей, предоставляемых JdbcTemplate. См. об этом в сопутствующем javadoc.

Построение запросов (SELECT)

Следующий запрос позволяет получить количество строк в отношении:

Java
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
Kotlin
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!

В следующем запросе используется переменная связывания:

Java
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
        "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
Kotlin
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
        "select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!

Следующий запрос ищет String:

Java
String lastName = this.jdbcTemplate.queryForObject(
        "select last_name from t_actor where id = ?",
        String.class, 1212L);
Kotlin
val lastName = this.jdbcTemplate.queryForObject<String>(
        "select last_name from t_actor where id = ?",
        arrayOf(1212L))!!

Следующий запрос находит и заполняет один объект предметной области:

Java
Actor actor = jdbcTemplate.queryForObject(
        "select first_name, last_name from t_actor where id = ?",
        (resultSet, rowNum) -> {
            Actor newActor = new Actor();
            newActor.setFirstName(resultSet.getString("first_name"));
            newActor.setLastName(resultSet.getString("last_name"));
            return newActor;
        },
        1212L);
Kotlin
val actor = jdbcTemplate.queryForObject(
            "select first_name, last_name from t_actor where id = ?",
            arrayOf(1212L)) { rs, _ ->
        Actor(rs.getString("first_name"), rs.getString("last_name"))
    }

Следующий запрос находит и заполняет список объектов предметной области:

Java
List<Actor> actors = this.jdbcTemplate.query(
        "select first_name, last_name from t_actor",
        (resultSet, rowNum) -> {
            Actor actor = new Actor();
            actor.setFirstName(resultSet.getString("first_name"));
            actor.setLastName(resultSet.getString("last_name"));
            return actor;
        });
Kotlin
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
        Actor(rs.getString("first_name"), rs.getString("last_name"))

Если бы последние два фрагмента кода действительно существовали в одном приложении, то имело бы смысл удалить дублирование, присутствующее в двух лямбда-выражениях RowMapper, и извлечь их в одно поле, на которое затем можно было бы ссылаться методами DAO по мере необходимости. Например, предыдущий фрагмент кода лучше записать следующим образом:

Java
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
    return actor;
};
public List<Actor> findAllActors() {
    return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
Kotlin
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
    Actor(rs.getString("first_name"), rs.getString("last_name"))
}
fun findAllActors(): List<Actor> {
    return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}

Обновление (INSERT, UPDATE и DELETE) с помощью JdbcTemplate

Вы можете использовать метод update(..) для выполнения операций вставки, обновления и удаления. Значения параметров обычно предоставляются в виде аргументов переменных или, как вариант, в виде массива объектов.

В следующем примере показана вставка новой записи:

Java
this.jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling");
Kotlin
jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling")

В следующем примере показано обновление существующей записи:

Java
this.jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L);
Kotlin
jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L)

В следующем примере показано удаление записи:

Java
this.jdbcTemplate.update(
        "delete from t_actor where id = ?",
        Long.valueOf(actorId));
Kotlin
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())

Другие операции JdbcTemplate

Можно использовать метод execute(..) для выполнения произвольного SQL. Следовательно, этот метод часто используется для стейтментов на языке определения данных (Data Definition Language /DDL). Он сильно перегружен вариантами, которые принимают интерфейсы обратного вызова, привязывают массивы переменных и так далее. В следующем примере создается таблица:

Java
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
Kotlin
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")

В следующем примере вызывается хранимая процедура:

Java
this.jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        Long.valueOf(unionId));
Kotlin
jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        unionId.toLong())

Усовершенствованная поддержка хранимых процедур описывается дальше.

Оптимальные методы работы с JdbcTemplate

После завершения конфигурирования экземпляры класса JdbcTemplate являются потокобезопасными. Это важно, поскольку означает, что можно сконфигурировать один экземпляр JdbcTemplate и затем безопасно внедрить эту общую ссылку в несколько DAO (или репозиториев). JdbcTemplate сохраняет состояние, поскольку хранит ссылку на DataSource, но это состояние не является диалоговым состоянием.

Оптимальным методом работы при использовании класса JdbcTemplate (и связанного с ним класса NamedParameterJdbcTemplate ) является конфигурирование DataSource в конфигурационном файле Spring, а затем внедрение зависимости этого общего бина DataSource в классы DAO. JdbcTemplate создается в сеттере для DataSource. Это приводит к появлению DAO примерно следующего вида:

Java
public class JdbcCorporateEventDao implements CorporateEventDao {
    private JdbcTemplate jdbcTemplate;
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    // Реализации методов CorporateEventDao с поддержкой JDBC следуют...
}
Kotlin
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
    private val jdbcTemplate = JdbcTemplate(dataSource)
    // Реализации методов CorporateEventDao с поддержкой JDBC следуют...
}

В следующем примере показана соответствующая XML-конфигурация:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <context:property-placeholder location="jdbc.properties"/>
</beans>

Альтернативой явной конфигурации является использование сканирования компонентов и поддержка аннотаций для внедрения зависимостей. В этом случае вы можете пометить класс аннотацией @Repository (что делает его кандидатом на сканирование компонентов) и пометить метод установки DataSource аннотацией @Autowired. В следующем примере показано, как это сделать:

Java
@Repository 
public class JdbcCorporateEventDao implements CorporateEventDao {
    private JdbcTemplate jdbcTemplate;
    @Autowired 
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
    }
    // Реализации методов CorporateEventDao с поддержкой JDBC следуют...
}
  1. Аннотируем класс с помощью @Repository.
  2. Аннотируем сеттер DataSource с помощью @Autowired.
  3. Создаем новый JdbcTemplate с использованием DataSource.
Kotlin
@Repository 
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { 
    private val jdbcTemplate = JdbcTemplate(dataSource) 
    // Реализации методов CorporateEventDao с поддержкой JDBC следуют...
}
  1. Аннотируем класс с помощью @Repository.
  2. Constructor injection of the DataSource.
  3. Создаем новый JdbcTemplate с использованием DataSource.

В следующем примере показана соответствующая XML-конфигурация:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- Поиск в базовом пакете приложения классов с аннотацией @Component для конфигурирования в качестве бинов  -->
    <context:component-scan base-package="org.springframework.docs.test" />
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <context:property-placeholder location="jdbc.properties"/>
</beans>

Если вы используете класс JdbcDaoSupport из Spring, а различные классы DAO с поддержкой JDBC примыкают к нему, то ваш подкласс наследует метод setDataSource(..) от класса JdbcDaoSupport. Вы можете определять, наследовать ли от этого класса. Класс JdbcDaoSupport указывается только для удобства.

Независимо от того, какой из описанных выше стилей инициализации шаблона вы решите использовать (или не использовать), редко возникает необходимость создавать новый экземпляр класса JdbcTemplate всякий раз, когда нужно выполнить SQL. После завершения конфигурирования экземпляр JdbcTemplate является потокобезопасным. Если ваше приложение обращается к нескольким базам данных, то вам может понадобиться несколько экземпляров JdbcTemplate, что требует наличия нескольких DataSources и, соответственно, нескольких по-разному сконфигурированных экземпляров JdbcTemplate.