Обновление записи через PUT-запрос в Spring Boot

Вернуться назад

25 апреля 2018

В статье Работа с БД в Spring Boot на примере postgresql мы узнали как читать данные из БД. Но чтение данных - это лишь малая часть всех операций, которые встречаются в типичном java-приложении. Теперь попробуем создать полноценный rest-интерфейс для обновления ранее добавленных записей.

За основу возьмём наше приложение из указанной статьи. Оно состоит из трёх слоёв: dao (работа с БД), бизнес-логика приложения (service) и сам rest-интерфейс (controller), который обрабатывает входящий json и генерирует исходящий.

Начнём с доработки dao-слоя (интерфейс ProfileDao).

    void updateProfile(String firstName, String secondName, int age, int id);

Для обновления нам потребуется указать id записи, а также остальные значимые поля.

В реализацию интерфейса dao (ProfileDaoImpl) добавим sql-запрос в виде константы, которую принято размещать в начале класса:

    private static final String SQL_UPDATE_PROFILE =
            "update profiles set first_name = :firstName, last_name = :lastName, age = :age where id = :id";

Имена параметров, которые мы будем подставлять в sql-запрос, начинаются с двоеточия.

Вот реализация обновления записи в БД:

    @Override
    public void updateProfile(String firstName, String lastName, int age, int id) {
        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("firstName", firstName);
        params.addValue("lastName", lastName);
        params.addValue("age", age);
        params.addValue("id", id);
        jdbcTemplate.update(SQL_UPDATE_PROFILE, params);
    }

Всё предельно просто: сначала собираем параметры, затем передаём их вместе с запросом в метод jdbcTemplate.update(), который вопреки своему названию отвечает вообще за любые изменения данных (и insert, и update, и delete). Обратите внимание, что jdbcTemplate у нас имеет тип NamedParameterJdbcTemplate - именно он позволяет использовать именованные параметры. Иначе пришлось бы писать знаки вопроса.

Перейдём к сервисному слою. Интерфейс ProfileService не отличается оригинальностью:

    void updateProfile(String firstName, String secondName, int age, int id);

Его реализация в ProfileServiceImpl:

    @Override
    public void updateProfile(String firstName, String secondName, int age, int id) {
        Profile profile = profileDao.getProfileById(id)
                .orElseThrow(() -> new ProfileNotFoundException(id));
        profileDao.updateProfile(firstName, secondName, age, profile.getId());
    }

Здесь операцию нужно выполнить над уже существующей записью, поэтому перед вызовом надо проверить её наличие в БД. Если по каким-то причинам её там не нашлось - сразу кидаем исключение, которое будет преобразовано в соответствующий json благодаря ErrorController, который мы рассматривали в предыдущей статье.

Согласно архитектуре restful-сервисов, чтение данных мы делаем при помощи GET-запросов, а изменение - при помощи PUT. Также хотелось бы отметить, что у GET-запроса не может быть тела запроса (body), все его параметры перечисляются в строке запроса. А PUT-запрос может иметь тело, в которое мы будем помещать целевой json.

Перейдём к нашему контроллеру ProfileController и добавим в него метод обновления профиля.

    @PutMapping(value = "/{personId:\\d+}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void updateProfile(
            @Valid @RequestBody ProfileRequest request,
            @PathVariable int personId
    ) {
        profileService.updateProfile(
            request.getFirstName(),
            request.getLastName(),
            request.getAge(),
            personId
        );
    }

В качестве аргумента метод принимает модель запроса ProfileRequest:

public class ProfileRequest {

    @NotNull
    private String firstName;

    @NotNull
    private String lastName;

    @NotNull
    @Min(1)
    private Integer age;

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

Эта модель представляет собой ровно тот объект, который мы ожидаем увидеть в теле запроса. Имена полей в json должны совпадать с именами полей в классе (при желании это можно переопределить дополнительной аннотацией). Дополнительно накладываем ограничения из пакета javax.validation.constraints на каждое поле.

Аннотация @NotNull применима для ссылочных типов и означает обязательность этого поля в запросе. Обратите внимание, что тут мы специально используем ссылочный Integer, который допускает null, а не примитивный int, который по умолчанию равен нулю. Ссылочный числовой тип вместе с @NotNull позволяет проверять обязательность числовых типов.

Аннотация @Min говорит сама за себя и ограничивает числовые типы снизу. Тем самым мы никогда не допустим, чтобы нам передали отрицательное число в качестве возраста.

Чтобы эти проверки работали для входящего json, параметр метода надо снабдить аннотацией @Valid, а чтобы именно в этот класс был преобразован json, также добавляем @ResponseBody. Внутри просто вызываем сервисный слой.

Аннотация @PutMapping говорит, что это обработчик PUT-запроса, причём в адресной строке также требуется указать personId (после двоеточия указана регулярка, т.е. мы ожидаем любое количество цифр). Значение этого параметра будет помещено в соответствующую переменную благодаря @PathVariable. В ответ метод будет возвращать 204 - «No Content».

Теперь мы готовы к тому, чтобы выполнить rest-запрос на создание новой записи в БД. Запускаем приложение и отравляем указанный PUT-запрос по адресу http://127.0.0.1:8080/profile/1, где 1 - это номер существующей записи. В http-заголовках обязательно указываем Content-Type: application/json.

{
  "firstName""Петр",
  "lastName""Сидоров",
  "age"14
}

В ответ в случае успеха получаем http-статус 204. Если же мы попытаемся нарушить наши ограничения по полям, то получим json с подробным описанием ошибки. А если укажем id, которой нет в БД, то получим следующий ответ:

{
  "message""Profile with id = 123 not found"
}

Таким образом, Spring Boot позволяет буквально за 5 минут создать полноценный обработчик PUT-запроса с валидацией входящих параметров.

Тэги: Java, SQL, Spring Boot, rest.


Исходники


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

Ваше имя:
Текст комментария: