В этой статье рассмотрим, как с помощью JdbcTemplate обращаться к базе и выполнять SQL-операторы. В следующей статье речь пойдет о более продвинутом CrudRepository и других возможностях Spring Data JDBC.
Maven-зависимость
Чтобы включить в Spring Boot проект Spring Data JDBC, добавим в POM-файл:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
Мы будем использовать базу H2, поэтому добавим также:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Но сначала рассмотрим пример подключения к базе и выполнения на ней select из обычного не Spring приложения. Это поможет понять, что JdbcTemplate делает под капотом.
Без JdbcTemplate
Допустим у нас есть сущность Animal:
public class Animal {
private long id;
private String name;
// getters/setters/constructors
}
В базе она хранится в таблице animal. Чтобы соединиться с базой и выбрать animal c id=1 в простом приложении без Spring, необходимо выполнить кучу действий:
public class JdbcMain {
public static void main(String[] args) {
Animal animal = findById(1l);
}
public static Animal findById(long id) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/dbname", "postgres", "password");
statement = connection.prepareStatement(
"select id, name from animal where id=?");
statement.setLong(1, id);
resultSet = statement.executeQuery();
Animal animal = null;
if (resultSet.next()) {
animal = new Animal(
resultSet.getInt("id"),
resultSet.getString("name"));
}
return animal;
} catch (SQLException e) {
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
}
}
}
return null;
}
}
В коде выше:
- Открывается соединение Connection.
- Создается PreparedStatement.
- Выполняется запрос.
- Идет итерация по ResultSet.
- затем ResultSet, PreparedStatement и Connection закрываются в блоке finally, причем эти действия тоже могут выбросить исключение.
Собственно полезного кода — пара строк, остальное — подготовка (boilerlate).
JdbcTemplate берет всю дополнительную работу на себя, на нас остается уникальный код — конкретный запрос.
C JdbcTemplate (точнее, лучше NamedParameterJdbcTemplate)
Мы будем использовать NamedParameterJdbcTemplate, который отличается от JdbcTemplate тем, что параметры можно задавать именем, а не знаком «?«.
Рассмотрим тот же пример выборки Animal по id c NamedParameterJdbcTemplate:
@Repository
public class AnimalRepository {
private final NamedParameterJdbcTemplate jdbc;
@Transactional
public Animal getById(long id) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
return jdbc.queryForObject("select * from animal where id=:id", params, new AnimalMapper());
}
//...
}
где AnimalMapper:
public class AnimalMapper implements RowMapper<Animal> {
@Override
public Animal mapRow(ResultSet resultSet, int i) throws SQLException {
final int id = resultSet.getInt("id");
final String name = resultSet.getString("name");
return new Animal(id, name);
}
}
Как видите, так все выглядит гораздо проще.
- Мы аннотировали репозиторий с помощью @Repository — так он стал бином.
- Внедрили NamedParameterJdbcTemplate.
- И выполнили запрос.
- Параметром передали AnimalMapper, который преобразует ResultSet в Animal.
Настройка соединения
Для соединения с базой Spring Boot создает бин dataSource.
Параметры доступа к базе прописываем в application.yml:
spring:
h2:
console:
enabled: true
path: /h2-console
datasource:
url: jdbc:h2:mem:testdb
username: sa
password:
Кроме того, мы включили h2-console, чтобы по адресу
localhost:8080/h2-console
можно было соединиться с базой после запуска приложения.
Создание и заполнение базы
Чтобы база создавалась и наполнялась, просто поместим в ресурсы файлы
schema.sql data.sql
с SQL-операторами, создающими таблицы и добавляющими данные соответственно.

Поскольку мы используем in-memory базу, данные в ней пропадают и создаются заново при перезапуске приложения.
Методы добавления, редактирования, выбора всех строк
И рассмотрим еще несколько методов.
@Transactional
public Animal insert(Animal animal) {
Map<String, Object> params = new HashMap<>();
params.put("name", animal.getName());
SqlParameterSource paramSource = new MapSqlParameterSource(params);
GeneratedKeyHolder holder = new GeneratedKeyHolder();
jdbc.update("insert into animal (name) values (:name)", paramSource, holder);
animal.setId(holder.getKey().longValue());
return animal;
}
@Transactional
public int update(String name, long id) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
params.put("id", id);
int n= jdbc.update("update animal set name=:name where id=:id", params);
return n ;
}
@Transactional
public List<Animal> getAll() {
return jdbc.query("select * from animal", new AnimalMapper());
}
Как видите, запросы, модифицирующие базу, делаются в методе jdbc.update().
При добавлении строки с помощью GeneratedKeyHolder мы получаем автоматически генерируемый в базе первичный ключ.
@Transactional
Все методы стоит аннотировать @Transactional, чтобы они выполнялись в рамках одной транзакции (на случай добавления других запросов в метод).
Итоги
Исходный код примера доступен на GitHub.