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
)
Наступний запит дозволяє отримати кількість рядків у відношенні:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
У наступному запиті використовується змінна зв'язування:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) від t_actor where first_name = ?", Integer.class, "Joe");
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
"select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
Наступний запит шукає String
:
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);;
val lastName = this.jdbcTemplate.queryForObject<String>(
"select last_name from t_actor where id = ?",
arrayOf(1212L))!!
Наступний запит знаходить і заповнює один об'єкт предметної області:
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);
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"))
}
Наступний запит знаходить і заповнює список об'єктів предметної області:
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;
});
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
Actor(rs.getString("first_name"), rs.getString("last_name"))
Якби останні два фрагменти коду дійсно існували в одному додатку, мало б сенс видалити дублювання, присутнє у
двох лямбда-виразах RowMapper
, і витягти їх в одне поле, на яке потім можна було б посилатися
методами DAO в разі необхідності. Наприклад, попередній фрагмент коду краще записати так:
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);
}
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(..)
для виконання операцій вставлення, оновлення та
видалення. Значення параметрів зазвичай надаються у вигляді змінних аргументів або, як варіант, у вигляді
масиву об'єктів.
У наступному прикладі показана вставка нового запису:
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling")
У наступному прикладі показано оновлення існуючого запису:
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L)
this.jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
Інші операції JdbcTemplate
Можна використовувати метод execute(..)
для виконання довільного SQL. Отже, цей метод часто
використовується для стейтментів мовою визначення даних (Data Definition Language/DDL). Він сильно
перевантажений варіантами, які приймають інтерфейси зворотного виклику, прив'язують масиви змінних тощо. У
цьому
прикладі створюється таблиця:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
У наступному прикладі викликається збережена процедура:
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));
jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
unionId.toLong())
Удосконалена підтримка збережених процедур описується далі.
Оптимальні методи роботи з JdbcTemplate
Після завершення конфігурування екземпляри класу JdbcTemplate
є потокобезпечними. Це важливо,
оскільки означає, що можна налаштувати один екземпляр JdbcTemplate
і потім безпечно впровадити
це
загальне посилання до кількох DAO (або репозиторіїв). JdbcTemplate
зберігає стан, оскільки
зберігає
посилання на DataSource
, але цей стан не є діалоговим станом.
Оптимальним методом роботи під час використання класу JdbcTemplate
(і пов'язаного з ним класу NamedParameterJdbcTemplate
)
є конфігурація DataSource
у конфігураційному файлі Spring, а потім — впровадження залежності
цього
спільного біна DataSource
до класів DAO. JdbcTemplate
створюється в сетері для
DataSource
.
Це призводить до появи DAO приблизно такого вигляду:
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// Реалізації методів CorporateEventDao з підтримкою JDBC слідують...
}
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
.
У цьому прикладі показано, як це зробити:
@Repository
public class JdbcCorporateEventDao implements CorporateEventDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// Реалізації методів CorporateEventDao з підтримкою JDBC слідують...
}
- Анотуємо клас за допомогою
@Repository
. - Анотуємо сетер
DataSource
за допомогою@Autowired
. - Створюємо новий
JdbcTemplate
за допомогоюDataSource
.
@Repository
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
// Реалізації методів CorporateEventDao з підтримкою JDBC слідують...
}
- Анотуємо клас за допомогою
@Repository
. - Constructor injection of the
DataSource
. - Створюємо новий
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
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ