HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Типовая задача в веб-разработке - это создание API для CRUD сущности.</p>
1 <p>Типовая задача в веб-разработке - это создание API для CRUD сущности.</p>
2 <p><strong>CRUD</strong>(<em>Create/Read/Update/Delete</em>) - это набор типовых операций, который обычно выполняется над сущностью. В этом уроке мы соберем все изученное и создадим эталонный CRUD на примере поста в блог.</p>
2 <p><strong>CRUD</strong>(<em>Create/Read/Update/Delete</em>) - это набор типовых операций, который обычно выполняется над сущностью. В этом уроке мы соберем все изученное и создадим эталонный CRUD на примере поста в блог.</p>
3 <p>Общий план создания CRUD выглядит так:</p>
3 <p>Общий план создания CRUD выглядит так:</p>
4 <ol><li>Создаем сущность</li>
4 <ol><li>Создаем сущность</li>
5 <li>Создаем репозиторий</li>
5 <li>Создаем репозиторий</li>
6 <li>Создаем DTO</li>
6 <li>Создаем DTO</li>
7 <li>Создаем маппер</li>
7 <li>Создаем маппер</li>
8 <li>Пишем тест</li>
8 <li>Пишем тест</li>
9 <li>Реализуем контроллер</li>
9 <li>Реализуем контроллер</li>
10 </ol><h2>Создание сущности</h2>
10 </ol><h2>Создание сущности</h2>
11 <p>У поста в блоге есть четыре основных свойства:</p>
11 <p>У поста в блоге есть четыре основных свойства:</p>
12 <ul><li>Название</li>
12 <ul><li>Название</li>
13 <li>Слаг</li>
13 <li>Слаг</li>
14 <li>Текст</li>
14 <li>Текст</li>
15 <li>Автор</li>
15 <li>Автор</li>
16 </ul><p>Кроме того, еще можно добавить дату создания и последнего обновления:</p>
16 </ul><p>Кроме того, еще можно добавить дату создания и последнего обновления:</p>
17 <h2>Создание репозитория</h2>
17 <h2>Создание репозитория</h2>
18 <p>Использование слага в URL-адресе подразумевает, что мы сможем делать выборку сущности по слагу:</p>
18 <p>Использование слага в URL-адресе подразумевает, что мы сможем делать выборку сущности по слагу:</p>
19 <p>Сразу добавим этот метод в репозиторий:</p>
19 <p>Сразу добавим этот метод в репозиторий:</p>
20 <h2>Создание DTO</h2>
20 <h2>Создание DTO</h2>
21 <p>В целом нам понадобятся три разных DTO - для создания, обновления и просмотра поста:</p>
21 <p>В целом нам понадобятся три разных DTO - для создания, обновления и просмотра поста:</p>
22 <h2>Создание маппера</h2>
22 <h2>Создание маппера</h2>
23 <p>Для CRUD нам нужны три операции:</p>
23 <p>Для CRUD нам нужны три операции:</p>
24 <ul><li>Конвертация DTO для создания в пост</li>
24 <ul><li>Конвертация DTO для создания в пост</li>
25 <li>Конвертация поста в DTO для просмотра</li>
25 <li>Конвертация поста в DTO для просмотра</li>
26 <li>Обновление поста на основе DTO для обновления</li>
26 <li>Обновление поста на основе DTO для обновления</li>
27 </ul><p>Самое интересное в этом коде - это конвертация authorId из DTO в свойство author внутри поста. Чтобы выполнить эту операцию, нужно сделать запрос в базу данных и извлечь объект автора. По умолчанию MapStruct такого не умеет, но для него можно создать маппер, который решает эту задачу. Здесь мы сразу приведем его код:</p>
27 </ul><p>Самое интересное в этом коде - это конвертация authorId из DTO в свойство author внутри поста. Чтобы выполнить эту операцию, нужно сделать запрос в базу данных и извлечь объект автора. По умолчанию MapStruct такого не умеет, но для него можно создать маппер, который решает эту задачу. Здесь мы сразу приведем его код:</p>
28 <p>Чтобы этот код заработал, необходимо внедрить общий базовый интерфейс для всех моделей, на который маппер мог бы ориентироваться и понимать, применять метод toEntity(). Мы назовем его BaseEntity:</p>
28 <p>Чтобы этот код заработал, необходимо внедрить общий базовый интерфейс для всех моделей, на который маппер мог бы ориентироваться и понимать, применять метод toEntity(). Мы назовем его BaseEntity:</p>
29 <p>Определение класса Post в таком случае выглядит так:</p>
29 <p>Определение класса Post в таком случае выглядит так:</p>
30 <h2>Создание теста</h2>
30 <h2>Создание теста</h2>
31 <p>Код интеграционных тестов не завязан на устройство контроллера. Поэтому сам тест можно написать до реализации контроллера - так мы упростим создание контроллера и проверку его работоспособности. Такой подход называется<strong>TDD</strong>(<em>Test Driven Development</em>):</p>
31 <p>Код интеграционных тестов не завязан на устройство контроллера. Поэтому сам тест можно написать до реализации контроллера - так мы упростим создание контроллера и проверку его работоспособности. Такой подход называется<strong>TDD</strong>(<em>Test Driven Development</em>):</p>
32 <h2>Реализация контроллера</h2>
32 <h2>Реализация контроллера</h2>
33 <p>Перейдем к контроллеру. Здесь мы не добавляем ничего нового. Весь его код мы видели частями, а теперь собираем все вместе:</p>
33 <p>Перейдем к контроллеру. Здесь мы не добавляем ничего нового. Весь его код мы видели частями, а теперь собираем все вместе:</p>
34 <p>В целом, CRUD не ограничивается только перечисленными методами. По ситуации методов может быть больше:</p>
34 <p>В целом, CRUD не ограничивается только перечисленными методами. По ситуации методов может быть больше:</p>
35 <ul><li>Если у нас есть какое-то особое обновление и вывод списка</li>
35 <ul><li>Если у нас есть какое-то особое обновление и вывод списка</li>
36 <li>Если у нас есть несколько контроллеров для одной и той же сущности (например, для управления постами для пользователей и для администраторов нужно два разных контроллера)</li>
36 <li>Если у нас есть несколько контроллеров для одной и той же сущности (например, для управления постами для пользователей и для администраторов нужно два разных контроллера)</li>
37 </ul>
37 </ul>