В этой статье рассмотрим, как с помощью 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.