Статьи
Утилиты Telegram YouTube Отзывы

Виды RowMapper в Spring

Видеогайд Исходники

2 мая 2023

Тэги: Collections, Hibernate, json, rest, Spring, SQL.

Содержание

  1. ColumnMapRowMapper
  2. BeanPropertyRowMapper
  3. DataClassRowMapper
  4. SingleColumnRowMapper
  5. Своя реализация RowMapper

Spring Framework – это один из самых популярных Java-фреймворков, который предоставляет набор инструментов и возможностей для разработки веб-приложений. Одним из таких инструментов является интерфейс RowMapper, который используется при чтении данных из БД в объекты Java.

В Spring Framework существует несколько стандартных реализаций интерфейса RowMapper, каждый из которых предоставляет свой набор функциональности и возможностей. Рассмотрим каждый из них подробнее на конкретном примере.

Предположим, что в БД у нас есть таблица city, которая содержит информацию о городах. В этой таблице есть 3 поля:

  • уникальный идентификатор города (id)
  • название (name)
  • количество жителей (population)

При чтении списка таких городов из БД средствами Spring JDBC, наш репозиторий будет выглядеть так:

@Repository
public class CityRepository {

    private final NamedParameterJdbcTemplate jdbcTemplate;

    public CityRepository(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    // ...
}

Здесь мы внедряем NamedParameterJdbcTemplate – компонент для работы с базой, который поддерживает именованные параметры.

ColumnMapRowMapper

Реализуем метод чтения с помощью ColumnMapRowMapper. Будем возвращать список городов, отсортированных по алфавиту.

public List<Map<String, Object>> getParamMaps() {
    return jdbcTemplate.query(
            "select id, name, population from city order by name",
            new ColumnMapRowMapper()
    );
}

Для каждой строки из результирующего набора ResultSet он будет возвращать мапу, в которой ключами будут названия колонок в таблице, причём в верхнем регистре. Если мы вызовем этот метод через RestController, то получим результат в таком формате:

[
    {
        "ID": 3,
        "NAME": "Екатеринбург",
        "POPULATION": 1493749
    },
    {
        "ID": 4,
        "NAME": "Нижний Новгород",
        "POPULATION": 1259013
    }
]

Отсюда следует вывод, что при добавлении новых полей в sql-запрос, ваш rest api автоматически расширится. С одной стороны это удобно, но с другой – не очень хорошо внутреннюю структуру БД отдавать наружу.

BeanPropertyRowMapper

Давайте теперь отделим структуру базы данных от API и создадим специальный класс для передачи данных в Json. Это будет обычный класс City с приватными полями, геттерами и сеттерами.

public class City {

    private int id;
    private String name;
    private int population;

    // get- и set-методы...
}

Обратите внимание, что в этом классе нет конструктора с параметрами.

Тогда при чтении данных мы можем использовать BeanPropertyRowMapper, который будет связывать имена полей в таблице и имена полей в классе автоматически.

public List<City> getAllBeansOrderedByName() {
    return jdbcTemplate.query(
            "select id, name, population from city order by name",
            new BeanPropertyRowMapper<>(City.class)
    );
}

Мы создаём экземпляр этого класс, типизируя его нашим City. Результат будет примерно такой:

[
    {
        "id": 3,
        "name": "Екатеринбург",
        "population": 1493749
    },
    {
        "id": 4,
        "name": "Нижний Новгород",
        "population": 1259013
    }
]

Уже лучше. Однако чтобы BeanPropertyRowMapper работал корректно, в классе должен быть конструктор по умолчанию и set-методы. Это несколько нарушает современные практики неизменяемости объектов. Как решить эту проблему?

DataClassRowMapper

В Java 17 появились record-классы. Они позволяют писать меньше кода, т.к. компилятор автоматически создаст для них get- и set-методы, метод toString(), а также конструктор с параметрами. В нашем случае record будет выглядеть так:

public record CityRecord(
        int id,
        String name,
        int population
) {
}

Согласитесь, он более компактный? Однако BeanPropertyRowMapper с ним работать не будет, т.к. нет конструктора по умолчанию. Специально для этих целей в Spring есть его наследник – DataClassRowMapper.

public List<CityRecord> getAllRecordsOrderedByName() {
    return jdbcTemplate.query(
            "select id, name, population from city order by name",
            new DataClassRowMapper<>(CityRecord.class)
    );
}

Формат ответа в json останется без изменений, а кода мы напишем меньше. Поэтому в настоящее время DataClassRowMapper предпочтительнее, чем BeanPropertyRowMapper.

SingleColumnRowMapper

А что, если нам вовсе не требуется сущность города целиком, а мы хотим просто возвращать их имена как список строк? Не проблема, SingleColumnRowMapper нам в этом поможет.

public List<String> getAllNames() {
    return jdbcTemplate.query(
            "select name from city order by name",
            new SingleColumnRowMapper<>(String.class)
    );
}

Выбираем из таблицы только одну колонку и типизируем SingleColumnRowMapper типом String. Тогда в ответе получим то, то нам надо:

[
    "Екатеринбург",
    "Нижний Новгород"
]

Своя реализация RowMapper

Наконец, чтобы получить максимальный контроль над маппингом полей из ResultSet, мы всегда можем создать собственный RowMapper, типизировав его нашим CityRecord. Предположим, что в имени мы хотим в круглых скобках отображать также его id:

public class CityRowMapper implements RowMapper<CityRecord> {
    @Override
    public CityRecord mapRow(ResultSet rs, int rowNum) throws SQLException {
        var id = rs.getInt("id");
        return new CityRecord(
                id,
                String.format("%s (%s)", rs.getString("name"), id),
                rs.getInt("population")
        );
    }
}

Мы определяем метод mapRow(), который работает в контексте одной строки из результирующего набора данных и возвращает объект CityRecord.

Теперь вернёмся в репозиторий и используем наш собственный RowMapper:

public List<CityRecord> getCustomRecordsOrderedByName() {
    return jdbcTemplate.query(
            "select id, name, population from city order by name",
            new CityRowMapper()
    );
}

Ответ в json будет таким:

[
    {
        "id": 3,
        "name": "Екатеринбург (3)",
        "population": 1493749
    },
    {
        "id": 4,
        "name": "Нижний Новгород (4)",
        "population": 1259013
    }
]

Таким образом, в 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.

Последние статьи


Комментарии

Добавить комментарий

×

devmark.ru