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, H2, Linux, Hibernate, Collections, Stream API, многопоточность, чат-боты, нейросети, файлы, devops, Docker, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.