Spring Data JPA

Spring Data JPA уменьшает шаблонный код вокруг JPA: создаёт реализации repository-интерфейсов, строит запросы и интегрирует их с транзакциями Spring. Hibernate обычно выступает JPA-провайдером, но Spring Data JPA не является ORM сам по себе.

Иерархия repository

  • Repository<T, ID> — marker и основа механизма proxy;
  • CrudRepository — базовые CRUD-операции;
  • PagingAndSortingRepository — страничная выборка и сортировка;
  • JpaRepository — JPA-специфичные операции, flush и batch-методы.

Интерфейс приложения может открыть только нужные операции:

interface OrderRepository extends Repository<Order, Long> {
    Optional<Order> findById(Long id);
    Order save(Order order);
}

Repository — граница доступа к агрегату, а не место для всей бизнес-логики.

Derived query

Page<Order> findByCustomerIdAndStatus(
        long customerId,
        OrderStatus status,
        Pageable pageable);

Spring разбирает имя метода и строит запрос. Это удобно для короткого условия. Если имя превращается в предложение, запрос лучше выразить JPQL, Specification или отдельной реализацией repository.

Явный запрос

@Query("""
    select o
    from Order o
    where o.createdAt >= :from
      and o.status in :statuses
    """)
Slice<Order> findRecent(
        Instant from,
        Set<OrderStatus> statuses,
        Pageable pageable);

Page выполняет дополнительный count query. Slice сообщает только наличие следующей страницы и может быть дешевле. Для сложного JPQL count-запрос иногда нужно задать отдельно.

Параметры связываются отдельно от текста запроса, что защищает значения от SQL инъекции. Динамические имена столбцов всё равно должны выбираться из разрешённого списка.

Projection

interface OrderRow {
    Long getId();
    OrderStatus getStatus();
    BigDecimal getTotal();
}
 
List<OrderRow> findByStatus(OrderStatus status);

Projection полезна для чтения, когда полный граф сущности не нужен. Для сложной read model явный DTO-конструктор делает выбранные поля заметнее.

Что делает save

Для новой сущности вызывается EntityManager.persist, для уже существующей — обычно merge. merge возвращает managed-копию; переданный detached-объект сам не становится managed.

Order managed = repository.save(detached);

Внутри транзакции managed-сущность часто не нужно повторно save: dirty checking сформирует UPDATE при flush. saveAndFlush не делает commit, а лишь заставляет синхронизировать persistence context с БД раньше.

Транзакции

CRUD-методы repository имеют транзакционные настройки, но use case из нескольких операций должен иметь границу на service:

@Transactional
public void confirm(long id) {
    Order order = orders.findById(id).orElseThrow();
    order.confirm();
    outbox.add(EventRecord.forOrder(order));
}

Так чтение, изменение и запись события относятся к одной транзакции. См. Transactional.

Блокировки

Оптимистическая блокировка обычно строится на @Version: конфликт обнаруживается при update/flush. Пессимистическую можно запросить явно:

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select o from Order o where o.id = :id")
Optional<Order> findForUpdate(long id);

Пессимистическая блокировка удерживается до завершения транзакции и может вызвать ожидание или deadlock. Её применяют только при понятной конкурентной модели.

Fetch-план

Repository не устраняет N+1 автоматически. Доступны:

  • join fetch в @Query;
  • @EntityGraph;
  • batch fetching;
  • projection.

Для pageable-запроса fetch join коллекции требует особой осторожности. Подробнее: Проблема N plus 1 Select Hibernate и Entity Graph.

На собеседовании

Чем Spring Data JPA отличается от Hibernate? Spring Data генерирует repository-слой и интеграцию; Hibernate реализует JPA и отображает объекты в SQL.

Почему saveAndFlush не гарантирует сохранность после сбоя? Flush отправляет SQL, но транзакция ещё может откатиться до commit.

Когда derived query становится плохим выбором? Когда имя трудно читать, нужны динамические условия, сложная агрегация, fetch-план или тонкая оптимизация SQL.

Связанные заметки: JPA Hibernate ORM, JPA Hibernate и JDBC сравнение интерфейсов, Изоляция транзакций.