Статьи
YouTube-канал

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

Исходники

24 апреля 2018

Тэги: Java rest Spring Boot SQL

В статье Работа с БД в 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-запроса с валидацией входящих параметров.


Облако тэгов

Kotlin, Java, Java 16, Java 11, Java 10, Java 9, Java 8, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Hibernate, Collections, Stream API, многопоточность, ввод-вывод, Apache, maven, gradle, JUnit, YouTube, новости, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml

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


Комментарии

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

×

devmark.ru