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(*) від 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.