Этап 4 - CRUD операции: Create, Read, Update, Delete
CRUD - это не просто четыре repository calls, а жизненный цикл данных вместе с validation, errors и transactions. Большинство бизнес-функций соединяют Create, Read, Update и Delete с validation, authorization, transactions и domain rules. В Spring Data JPA repositories дают низкоуровневые CRUD methods, а services решают, как использовать их безопасно.
Create и Read
Create означает вставку новой записи. В Java-коде обычно создают entity object, заполняют обязательные поля, проверяют бизнес-правила и вызывают save. Hibernate генерирует insert, когда у объекта еще нет database identity.
public User createUser(CreateUserRequest request) {
User user = new User(request.email(), request.name());
return userRepository.save(user);
}
Read означает загрузку данных. Чтение по id использует findById. Чтение списка может использовать findAll, но production API обычно должны применять pagination. Pageable описывает номер страницы, размер страницы и сортировку. Page<T> содержит данные и metadata: total elements, total pages и другую информацию.
public Page<Product> listProducts(Pageable pageable) {
return productRepository.findAll(pageable);
}
Update
Update обычно должен начинаться с загрузки существующей entity. Это важно, потому что строки в базе может не быть, а текущее состояние может понадобиться для validation. После загрузки entity внутри transaction меняют ее поля. Hibernate отслеживает managed entities и отправляет изменения при commit.
@Transactional
public User renameUser(Long id, String name) {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
user.setName(name);
return user;
}
Вызывать save после изменения managed entity часто не обязательно, но многие команды делают это ради явности. Главное - не обновлять данные через создание нового объекта только с id и несколькими полями, если ты не понимаешь merge behavior. Такой подход может случайно перезаписать поля значением null.
Delete и service layer
Delete удаляет данные. deleteById краткий, но для бизнес-операций часто лучше сначала загрузить entity, проверить правила и затем вызвать delete. Например, удаление товара может быть запрещено, если по нему есть завершенные заказы. Такие правила относятся к service layer, а не к controller.
Controllers должны преобразовывать HTTP input и output. Services должны координировать use cases. Repositories должны сохранять entities. Если CRUD-код находится прямо в controllers, error handling, validation и transactions расползаются по приложению.
| CRUD-сценарий | Правильный service flow | Частая ошибка новичка |
|---|---|---|
| Создать пользователя | Проверить request, создать новую entity, задать defaults, вызвать save. | Доверять полям request напрямую и забыть обязательные defaults вроде createdAt. |
| Прочитать одного пользователя | Вызвать findById, превратить empty Optional в domain error или HTTP 404. | Вызвать .get() и получить неясную runtime exception. |
| Получить список товаров | Принять Pageable, выбрать sorting, вернуть DTO page. | Вызвать findAll() и загрузить всю таблицу ради одного экрана. |
| Обновить цену товара | Загрузить существующую entity, проверить правила, изменить поля внутри transaction. | Создать новую partial entity и случайно перезаписать поля значением null. |
| Удалить запись | Загрузить entity, проверить, что удаление разрешено, затем удалить. | Удалять по id без проверки ownership, status или dependent records. |
Обработка ошибок
EntityNotFoundException или custom exception делает отсутствие данных явным. В REST API exception можно связать с HTTP 404 через @ControllerAdvice. Custom exceptions часто лучше, когда важен язык предметной области: UserNotFoundException или ProductNotFoundException.
Ошибки нельзя молча проглатывать. Если цель update не существует, API не должен тихо создавать другую строку, если feature явно не поддерживает upsert. Понятные ошибки упрощают debugging клиентов и предотвращают скрытую порчу данных.
Практика
Реализуй CRUD для пользователей и товаров. Для пользователей создай endpoints: create, get by id, list with pagination, rename и delete. Для товаров добавь обновление цены и availability. Оставь controllers тонкими: они вызывают services и возвращают DTOs. Поставь @Transactional на service methods, которые изменяют данные. Проверь missing ids и убедись, что они дают предсказуемый error response.
Чек-лист понимания
- Могу объяснить Create, Read, Update и Delete в терминах JPA.
- Понимаю, почему update обычно начинается с
findById. - Понимаю
PageableиPage. - Знаю, почему controllers не должны содержать CRUD business logic.
- Могу спроектировать понятный not-found flow.