Логотип Workflow

Article

Updated at:

Repositories

Этап 3 - Repositories: JpaRepository и базовый доступ к данным

Repository Pattern дает service интерфейс доступа к данным и скрывает повторяющиеся persistence operations. Service должен выражать бизнес-операцию, а не повторять SQL или вызовы EntityManager для каждого базового действия. Repository дает service понятный интерфейс для сохранения, загрузки, подсчета и удаления entities.

Repository pattern in Spring Data JPA

Что предоставляет JpaRepository

Spring Data JPA создает реализации repository во время выполнения. Разработчик объявляет interface, наследуется от JpaRepository и указывает тип entity и тип id. Для типовых операций отдельный class-implementation писать не нужно.

public interface UserRepository extends JpaRepository<User, Long> {
}

User - это entity. Long - тип ее primary key. Из такого маленького interface Spring Data дает методы save, findById, findAll, delete, deleteById, count, pagination methods, sorting methods и создание запросов по именам методов.

Repository обычно inject-ится в service. Это оставляет controllers тонкими и не дает database access code расползаться по web layer. Service решает, что означает операция; repository выполняет persistence.

Основные методы

save() сохраняет entity. Для новой entity Hibernate создает insert. Для существующей managed или merged entity он может создать update. Название метода широкое намеренно: JPA решает, новый объект или нет, на основе identifier и persistence state.

findById() ищет по primary key и возвращает Optional<T>. Метод не возвращает null, потому что отсутствие записи - ожидаемый результат, а не техническая авария. Вызывающий код обязан обработать оба случая: entity есть или entity нет.

findAll() возвращает все строки таблицы entity. Это удобно для демо и маленьких справочников, но опасно для больших production tables. Для реальных экранов и API лучше использовать pagination через Pageable.

delete() удаляет конкретную entity. deleteById() удаляет по primary key. count() возвращает количество строк. Методы простые, но они все равно выполняют SQL, поэтому важно помнить о transaction boundaries и размере данных.

Repository call в service codeЧто service должен решить до вызоваКакая SQL-работа скрыта за методом
save(entity)Валиден ли объект, и это create или update сценарий?Hibernate выбирает insert для новой entity или update для существующей.
findById(id)Что делать, если такого id нет?Выполняется select ... where id = ?, а результат заворачивается в Optional.
findAll()Достаточно ли маленькая таблица, чтобы грузить ее целиком?Выполняется full table query; без pagination это может быть опасно.
delete(entity)Разрешено ли удаление по business rules?Hibernate планирует delete по primary key entity.
count()Нужен ли точный count для этого экрана или правила?База выполняет select count(*), что на больших данных тоже может быть дорого.

Optional и отсутствующие данные

Optional - это контейнер, в котором значение может быть, а может отсутствовать. findById возвращает Optional, потому что запрошенный id может не существовать. Это заставляет разработчика принять решение: вернуть 404, выбросить domain exception или использовать fallback.

public User getUser(Long id) {
    return userRepository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
}

Не стоит вызывать .get() у Optional без проверки. Это просто переносит ошибку в менее понятное место. Service method должен преобразовать отсутствие данных в осмысленное поведение приложения.

Практика

Создай UserRepository extends JpaRepository<User, Long> и ProductRepository extends JpaRepository<Product, Long>. Напиши service method для создания пользователя, метод загрузки по id и метод получения paginated list товаров. Включи SQL logging и сравни repository-вызовы со сгенерированным SQL. Repository убирает boilerplate, но не отменяет необходимости понимать, какой запрос выполняется.

Чек-лист понимания

  • Могу объяснить, зачем repositories нужны вместо прямого database code в controllers.
  • Понимаю, что означает JpaRepository<User, Long>.
  • Могу описать save, findById, findAll, delete и count.
  • Понимаю, почему findById возвращает Optional.
  • Знаю, когда findAll рискован и почему нужна pagination.

Авторизуйтесь чтоб пройти тесты

Practice

Интерактивная практика

Выполните задания и сразу проверьте ответ.