Статьи
Утилиты 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. Выбор нужного типа зависит от конкретной задачи и требований к результатам запроса.



Комментарии

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

×

devmark.ru