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