Репозитории Spring Data: JpaRepository и магия методов

В Spring Data вы не пишете реализацию репозитория — только интерфейс. Spring сам генерирует CRUD и даже запросы по имени метода.
Суть: унаследуйте интерфейс от JpaRepository — и получите save, findById, findAll, delete бесплатно. Метод findByEmail(String) Spring сам превратит в SELECT ... WHERE email = ?.

Слой доступа к данным традиционно состоял из тонн однотипного кода: открыть соединение, подготовить запрос, пробежать по результату. Spring Data убирает этот шаблон почти полностью: вы описываете, ЧТО хотите получить, а не КАК это сделать.

Репозиторий = интерфейс

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // CRUD уже есть: save, findById, findAll, deleteById ...

    // Производные запросы — Spring сгенерирует SQL по имени
    Optional<User> findByEmail(String email);
    List<User> findByAgeGreaterThan(int age);
    List<User> findByNameContainingIgnoreCase(String part);
    boolean existsByEmail(String email);
}

Параметры JpaRepository<User, Long> означают: сущность User, тип ключа Long. Реализацию этого интерфейса Spring создаст сам в рантайме — вам её писать не нужно.

Производные запросы

Имя метода — это мини-язык запросов. Spring разбирает его на части: findBy + поле + условие. findByAgeGreaterThan читается как «найти, где age больше». Ключевые слова: And, Or, Between, Like, Containing, OrderBy, IgnoreCase.

Как работает под капотом

При старте Spring Data находит интерфейсы-репозитории и для каждого создаёт прокси — динамический объект-реализацию. Для каждого метода прокси либо берёт готовую реализацию CRUD, либо разбирает имя метода и строит JPQL/SQL. К моменту работы приложения у вас есть полноценный бин-репозиторий.

  interface UserRepository extends JpaRepository
        |  Spring Data при старте
        v
  создаёт ПРОКСИ-реализацию
        |
        +-- save/findById/...  -> готовый CRUD
        +-- findByEmail        -> разбор имени -> SELECT ... WHERE email = ?
        +-- findByAgeGreaterThan -> SELECT ... WHERE age > ?
        v
  бин UserRepository готов к внедрению

Смоделируем разбор имени метода в запрос-фильтр:

# "Производный запрос": имя метода -> фильтр над коллекцией
users = [
    {"name": "Анна", "email": "[email protected]", "age": 30},
    {"name": "Борис", "email": "[email protected]", "age": 25},
    {"name": "Анна", "email": "[email protected]", "age": 40},
]

def find_by_email(email):
    return [u for u in users if u["email"] == email]

def find_by_age_greater_than(age):
    return [u for u in users if u["age"] > age]

def find_by_name_containing(part):
    return [u for u in users if part.lower() in u["name"].lower()]

print(find_by_email("[email protected]"))
print(find_by_age_greater_than(28))
print(find_by_name_containing("ан"))

Нажмите «Попробуй сам ▶»: каждое «имя метода» превращается в фильтр. Spring делает то же, но переводит фильтр в SQL и выполняет в базе.

Частые ошибки

  • Опечатка в имени поля. findByEmial — Spring не найдёт поле и упадёт на старте (это лучше, чем в рантайме).
  • Слишком длинные имена. findByAAndBAndCOrderByD нечитаемо — для сложного лучше @Query.
  • findBy для одного, возвращающий List. Если ожидается один результат, возвращайте Optional<User>, а не список.

Best practices

  • Используйте производные запросы для простых условий — это лаконично и читаемо.
  • Для поиска одной записи возвращайте Optional, для проверки — existsBy.../countBy....
  • Когда имя метода становится громоздким, переходите на @Query (следующий урок).

Итог: репозиторий в Spring Data — это интерфейс без реализации. CRUD достаётся из коробки, а производные запросы генерируются по имени метода. Меньше кода — меньше ошибок.

Закрепим главное

Главная идея Spring Data — декларативность: вы описываете намерение, а не реализацию. Интерфейс-репозиторий без единой строки тела превращается в полноценный компонент с CRUD и осмысленными запросами. Это резко сокращает объём кода доступа к данным и убирает целый класс ошибок, которые раньше возникали при ручной работе с JDBC.

Полезно понимать границу применимости производных запросов. Пока условие простое — одно-два поля, понятное сравнение — имя метода читается отлично и не требует комментариев. Но как только в имени появляется три и более условий со связками And/Or, оно становится нечитаемым ребусом. Это сигнал перейти на @Query с явным JPQL. Выбирайте инструмент по сложности запроса: простое — именем метода, сложное — явным запросом. И помните про возвращаемые типы: для одной записи — Optional, для проверки наличия — existsBy, для подсчёта — countBy; это делает намерение метода однозначным.

Проверьте себя
1. Что нужно сделать, чтобы получить базовые операции CRUD (save, findById, findAll) для сущности User?
AНаписать класс с SQL-запросами
BОбъявить интерфейс, унаследованный от JpaRepository<User, Long>
CСоздать таблицу вручную
DРеализовать интерфейс полностью
2. Во что Spring Data превратит метод findByAgeGreaterThan(int age)?
AВ ошибку компиляции
BВ запрос SELECT ... WHERE age > ?
CВ удаление записей
DВ метод без реализации