24 марта 2023
Тэги: Collections, Kotlin, rest, Spring Boot, Spring Data, YouTube, руководство.
В предыдущей статье Spring Data JPA, REST и Kotlin: "один-ко-многим", чтение данных мы добавили к родительской сущности «Страна» дочернюю сущность «Город». В итоге у нас получилось отношение «один-ко-многим». В продолжение этой темы научимся создавать, изменять и удалять города вместе со странами в рамках одного запроса от клиента.
Ранее мы уже создали сущность для города под названием CityEntity:
Поскольку теперь мы хотим не только считывать информацию о городе вместе со страной, но и изменять её, нам нужно создать новый репозиторий CityRepository:
Этот репозиторий мы тоже наследуем от стандартного CrudRepository, типизируя его сущностью CityEntity. В интерфейс репозитория добавляем один кастомный метод deleteAllByCountry(), который будет удалять из таблицы city все города, связанные с указанной страной. Название этого метода соответствует соглашениям об именовании, поэтому Spring Data автоматически сгенерирует его реализацию.
В сервисном слое CountryServiceImpl у нас уже есть метод create(), принимающий в качестве параметра dto (data transfer object) со страной и городами.
Доработаем метод создания, чтобы он сохранял также и города.
Здесь мы сначала создаём новую страну. Репозиторий стран возвращает нам созданную сущность, в которой уже будет сгенерированный базой id. Затем мы проходимся по всем городам, пришедшим от клиента внутри dto и преобразуем их в сущность CityEntity с помощью метода расширения CityDto.toEntity(). Затем передаём полученный список сущностей городов в метод saveAll() репозитория городов (его нужно добавить в конструктор сервиса). Поскольку в данном методе несколько обращений к БД, то все запросы выполняются в рамках одной транзакции благодаря аннотации @Transactional.
Вспомогательный метод расширения выглядит так:
Теперь запускаем приложение и выполняем в Postman POST-запрос на создание новой страны и городов:
В ответ мы получим id новой страны, а в БД будет добавлена одна запись в таблицу country и 2 записи в таблицу city.
Метод update() в сервисном слое должен быть чуть хитрее. Модифицируем его так, чтобы он сначала искал страну, изменял её поля, затем удалял связанные с ней города и вставлял те, которые придут в dto.
Подход с удалением и последующей вставкой более прост, чем вычисление только тех городов, которые реально изменились. Для удаления городов мы используем добавленный нами ранее метод deleteAllByCountry(). Для вставки новых городов вызываем стандартный метод saveAll(), который доступен в нашем репозитории городов благодаря наследованию от CrudRepository. Все эти действия опять же делаем в транзакции.
Протестируем наш метод в Postman, отправив PUT-запрос:
Здесь я изменил название одного из городов и добавил ещё один новый. В результате будут удалены два существующих города и добавлены три новых, которые пришли в запросе.
Существующий метод удаления delete() в сервисном слое дорабатывается минимально:
По сути здесь мы перед удалением страны должны сначала удалить все связанные с ней города. То есть действуем в обратном порядке по сравнению со вставкой.
При выполнении DELETE-запроса тело можно вообще не указывать:
Запрос удаления может быть выполнен успешно только 1 раз, т.к. перед удалением мы каждый раз пытаемся проверять, существует ли указанная страна в БД.
Для изменения дочерних объектов, связанных с родительским, мы лишь добавили новый репозиторий и немного изменили сервисный слой. Сущности, dto и контроллер вообще не менялись. И при этом мы довольно просто обеспечили изменение дочерних сущностей в рамках одного запроса благодаря тому, что сначала удаляем все существующие связанные города, а затем добавляем те, которые пришли в запросе от клиента.
А в следующей статье Spring Data JPA, REST и Kotlin: проекции мы научимся оптимизировать sql, который генерирует Spring Data JPA, чтобы выбирать только те поля, которые нам действительно нужны.
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.