2 мая 2023
Тэги: Collections, Hibernate, json, rest, Spring, SQL.
Spring Framework – это один из самых популярных Java-фреймворков, который предоставляет набор инструментов и возможностей для разработки веб-приложений. Одним из таких инструментов является интерфейс RowMapper, который используется при чтении данных из БД в объекты Java.
В Spring Framework существует несколько стандартных реализаций интерфейса RowMapper, каждый из которых предоставляет свой набор функциональности и возможностей. Рассмотрим каждый из них подробнее на конкретном примере.
Предположим, что в БД у нас есть таблица city, которая содержит информацию о городах. В этой таблице есть 3 поля:
При чтении списка таких городов из БД средствами Spring JDBC, наш репозиторий будет выглядеть так:
Здесь мы внедряем NamedParameterJdbcTemplate – компонент для работы с базой, который поддерживает именованные параметры.
Реализуем метод чтения с помощью ColumnMapRowMapper. Будем возвращать список городов, отсортированных по алфавиту.
Для каждой строки из результирующего набора ResultSet он будет возвращать мапу, в которой ключами будут названия колонок в таблице, причём в верхнем регистре. Если мы вызовем этот метод через RestController, то получим результат в таком формате:
Отсюда следует вывод, что при добавлении новых полей в sql-запрос, ваш rest api автоматически расширится. С одной стороны это удобно, но с другой – не очень хорошо внутреннюю структуру БД отдавать наружу.
Давайте теперь отделим структуру базы данных от API и создадим специальный класс для передачи данных в Json. Это будет обычный класс City с приватными полями, геттерами и сеттерами.
Обратите внимание, что в этом классе нет конструктора с параметрами.
Тогда при чтении данных мы можем использовать BeanPropertyRowMapper, который будет связывать имена полей в таблице и имена полей в классе автоматически.
Мы создаём экземпляр этого класс, типизируя его нашим City. Результат будет примерно такой:
Уже лучше. Однако чтобы BeanPropertyRowMapper работал корректно, в классе должен быть конструктор по умолчанию и set-методы. Это несколько нарушает современные практики неизменяемости объектов. Как решить эту проблему?
В Java 17 появились record-классы. Они позволяют писать меньше кода, т.к. компилятор автоматически создаст для них get- и set-методы, метод toString(), а также конструктор с параметрами. В нашем случае record будет выглядеть так:
Согласитесь, он более компактный? Однако BeanPropertyRowMapper с ним работать не будет, т.к. нет конструктора по умолчанию. Специально для этих целей в Spring есть его наследник – DataClassRowMapper.
Формат ответа в json останется без изменений, а кода мы напишем меньше. Поэтому в настоящее время DataClassRowMapper предпочтительнее, чем BeanPropertyRowMapper.
А что, если нам вовсе не требуется сущность города целиком, а мы хотим просто возвращать их имена как список строк? Не проблема, SingleColumnRowMapper нам в этом поможет.
Выбираем из таблицы только одну колонку и типизируем SingleColumnRowMapper типом String. Тогда в ответе получим то, то нам надо:
Наконец, чтобы получить максимальный контроль над маппингом полей из ResultSet, мы всегда можем создать собственный RowMapper, типизировав его нашим CityRecord. Предположим, что в имени мы хотим в круглых скобках отображать также его id:
Мы определяем метод mapRow(), который работает в контексте одной строки из результирующего набора данных и возвращает объект CityRecord.
Теперь вернёмся в репозиторий и используем наш собственный RowMapper:
Ответ в json будет таким:
Таким образом, в Spring Framework существует несколько типов RowMapper, каждый из которых предоставляет свои возможности для маппинга данных ResultSet в объекты Java. Выбор нужного типа зависит от конкретной задачи и требований к результатам запроса.
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.