Запросы в Spring JDBC

RowMapperЗапросы к базе данных делаются с помощью JDBC гораздо проще.

Поскольку Spring сам может выполнять SQL скрипты при запуске приложения, для этой статьи я подготовил достаточно развесистую схему данных, чтобы примеры выглядели интереснее.

Удел Java обычно enterprise приложения, поэтому пример самый что ни на есть энтерпрайзный. Предположим, что мы пишем систему отслеживания заказов, в которой есть клиенты, товары и, собственно, заказы. Заказ принадлежит клиенту и связан со списком входящих в него товаров. Итого четыре таблицы, как показано на E/R диаграмме:

E/R диаграмма

По этой диаграмме я подготовил скрипт, который создаёт таблицы и наполняет их данными.

Простые запросы

Если запрос возвращает ровным счётом одно значение, то есть одну строку с единственной колонкой, его значение можно получить напрямую из метода queryForObject()

Достаточно лишь передать тип, к которому надо привести результат, и указать параметры запроса.

Если столбец всё ещё один, а строк много, можно запросить список объектов подходящего класса:

И в этом случае достаточно передать тип результата и указать параметры запроса, если они требуются.

Если же запрос возвращает обычную таблицу, в которой много строк и много столбцов, то можно попросить вернуть список key-value значений по строкам, как я писал раньше:

 

Object Mapping

Второй вариант — отображение строки на объект. Например, заказ состоит из номера заказа и заказчика, который, в свою очередь, состоит из номера заказчика и e-mail. Мы можем получить из метода query*() сразу готовый объект заказа, реализовав отображение данных в объекты.

Для начала создадим классы заказчика и заказа:

Я использовал project lombok, чтобы сделать их покороче.

Теперь, когда у нас есть готовые классы, напишем класс для отображения данных из SQL. Отображающий класс реализует интерфейс RowMapper  из Spring JDBC:

В интерфейсе RowMapper  необходимо реализовать метод mapRow(), который принимает в себя текущую строку в JDBC ResultSet и её номер, разбирает эту строку и, основываясь на её данных, собирает и возвращает объект. Готовый маппер используется в query*() методе так же, как раньше использовался конкретный класс:

Очевидно, что запрос должен возвращать именно те столбцы, которые ожидает получить маппер.

Используя маппер можно получить и коллекцию объектов, так же, как раньше получали коллекции базовых типов:

 

NamedParameterJDBCTemplate

Параметры не всегда удобно передавать используя лишь порядок. Во-первых легко ошибиться, поставив параметр не на то место, в котором его ожидает запрос, во-вторых, если в запросе один и тот же параметр используется более одного раза, его придётся повторять. Spring JDBC поддерживает именованные параметры во всех базах данных, используя для этого расширенный вариант JdbcTemplate — NamedParameterJdbcTemplate.

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

При исполнении запроса параметры запроса не передаются явно в функцию, а передаются либо в виде Map<String, Object> либо объекта класса SqlParameterSource

При таком использовании, как показано в примере выше, различий между Map<String, Object> и SqlParametersSource нет. Но, поскольку SqlParametersSource является интерфейсом, существуют более интересные его реализации. Например Sping может сам подставлять значения параметров, выбирая их из Java bean.

BeanPropertySqlParameterSource анализирует переданный ему объект и для каждого свойства объекта создаёт параметр с именем свойства и его значением.

SqlParametersSource можно реализовывать и самому. Для примера я сделаю реализацию, которая для любого имени параметра возвращает число 3:
Для реализации я использовал абстрактный класс AbstractSqlParameterSource, который реализует SqlParametersSource и пару вспомогательных методов. Для реализации собственного SqlParametersSource необходимо написать три метода:

  • hasValue() — который возвращает true, если параметр с таким именем у нас есть.
  • getValue() — который возвращает значение параметра с указанным именем.
  • getSqlType() — который возвращает SQL тип параметра с указанным именем.

Использовать его как и другие классы параметров:

Изменение данных

Данные из базы надо не только запрашивать, но и изменять. Методы update() служат именно для таких запросов и используются аналогично методам query*(): принимают запрос и параметры.

Параметры для update()  могут быть и именованными:

Prepared statements

Spring JDBC скрывает от конечного пользователя разницу между обычным запросом и prepared запросом. Методы execute()  и query*() автоматически создают из запросов PreparedStatement и сами управляют его жизненным циклом.

Но для тех, кто хочет опуститься уровнем ниже и самостоятельно поуправлять созданием PreparedStatement, Spring предоставляет callback интерфейсы для создания PreparedStatement и установки их параметров.

Для задания параметров используется интерфейс PreparedStatementSetter, в метод setValues() которого передаётся PreparedStatement из JDBC и метод должен наполнить его значениями. Экземпляры PreparedStatementSetter можно использовать там, где ожидаются параметры запроса.

Интерфейс PreparedStatementCreator позволяет полностью управлять процессом создания экземпляра PreparedStatement и установкой параметров. Метод createPreparedStatement()  должен возвращать готовый к выполнению запрос.

OrderCountHandler показывает использование callback интерфейса RowCallbackHandler для обработки результатов запроса. Его метод processRow() вызывается для каждой строки результата и получает JDBC ResultSet  в качестве аргумента.

Код примера доступен на github.