3 августа 2021
Тэги: Java, PostgreSQL, rest, Spring Boot, SQL.
Spring Boot предоставляет два интерфейса для обработки выборки из БД: RowMapper и ResultSetExtractor. Давайте разберём их назначение, а также выясним, чем они различаются на примере справочника городов и стран.
Чаще всего при работе со списками в restful-сервисах, построенных на Spring Boot, вы будете использовать RowMapper. Этот класс обрабатывает отдельно каждую запись, полученную из БД, и возвращает уже готовый объект – модель данных. В большинстве случаев его вполне хватает.
Создадим простенький rest-контроллер, который будет возвращать список всех стран. Эти страны будем хранить в БД. Определение таблицы в СУБД postgres выглядит следующим образом:
Здесь тип serial представляет собой обычный integer, который автоматически увеличивается на 1 при добавлении каждой новой записи. То есть нет нужды при вставке явно указывать id.
Добавим туда несколько стран для примера:
Наша модель данных Country по типу и набору полей должна соответствовать структуре таблицы:
Обработчик входящих запросов (контроллер) будет выглядеть так:
Здесь мы из контроллера сразу используем уровень работы с БД (GeoDao). В реальных приложениях между ними должен быть ещё сервисный слой, но для краткости я его пропущу, т.к. он довольно тривиален.
Слой работы с БД (dao):
Код маппера:
Здесь мы реализуем интерфейс RowMapper, типизированный нашей моделью Country. Каждую строку здесь обрабатываем отдельно при помощи ResultSet. В качестве результата возвращаем уже готовый объект.
Теперь мы готовы к тому, чтобы запустить наше приложение и выполнить GET-запрос по адресу http://127.0.0.1:8080/geo/countries. В ответ получим такой json:
Теперь создадим вторую таблицу с городами, причём каждый город привязан к одной из стран. То есть это отношение «один-ко-многим». Структура таблицы будет выглядеть так:
Связь с таблицей city происходит при помощи внешнего ключа country_id. Добавим в таблицу несколько записей:
Предположим, что мы хотим возвращать города в таком виде, чтобы они были сгруппированы по странам. Сделать это на уровне БД можно при помощи join. Обратите внимание, что сортируем мы по названию страны.
Таким образом, в каждой строке у нас будет и название города, и название страны, в которой этот город находится.
Очевидно, что для группировки по странам нужно работать в контексте всей выборки одновременно, однако RowMapper работает только на уровне одной строки. И тут нам на помощь приходит ResultSetExtractor.
Типизируем его составным объектом Map. Это справочник, где ключ – строка, а значение – список строк (List). Важно, что в качестве реализации интерфейса Map мы выбрали не HashMap, а LinkedHashMap, который сохраняет порядок добавления ключей. Это нам нужно для того, чтобы страны шли в алфавитном порядке.
Далее в цикле обходим все строки, пока они есть, при помощи rs.next(), каждый раз переходя на новую строку. Сначала берём из текущей строки страну, которая будет ключом в нашем справочнике. Затем добавляем этот ключ с пустым списком городов, если такого ключа ещё нет, при помощи удобного метода putIfAbsent(). Затем извлекаем из текущей строки название города и добавляем его в список соответствующей страны.
Метод в dao будет выглядеть довольно просто (не забудьте добавить sql-запрос, указанный выше):
В контроллере также не будет ничего сложного:
Теперь мы готовы выполнить GET-запрос на адрес http://127.0.0.1:8080/geo/cities и получить такой красивый json:
На данном примере наглядно видно, что если мы хотим одновременно извлекать данные из нескольких таблиц, то тут на помощь приходит ResultSetExtractor, который работает в контексте всей выборки целиком, позволяя группировать данные. А если мы работаем с одной таблицей, то тут вполне хватит и обычного RowMapper.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.