Какие есть способы построения сложных запросов в репозитории?
Что проверяют
Знаете ли вы весь набор инструментов Spring Data JPA и умеете ли выбрать под задачу: статичный запрос, динамический фильтр из десятка необязательных полей, отчёт на нативном SQL. Следом обычно спрашивают про пагинацию и N+1 в этих запросах.
Как отвечать
Идите от простого к сложному:
- derived queries:
findByStatusAndCreatedAtAfter(...). Удобно на двух-трёх условиях, дальше имя метода становится нечитаемым; @Queryс JPQL: запрос по сущностям с именованными параметрами:status, проекция в DTO или record черезselect new ...либо интерфейсная проекция;@Query(nativeQuery = true): оконные функции, CTE, операторы конкретной базы. Для пагинации нужен отдельныйcountQuery;@Modifying: массовыеUPDATEиDELETE, лучше сclearAutomatically = true, чтобы контекст персистентности не хранил устаревшие сущности;- Specifications: репозиторий расширяет
JpaSpecificationExecutor, условия собираются из маленькихSpecification<T>черезandиor. Стандартный ответ для динамических фильтров; - Criteria API через
EntityManagerв кастомном фрагменте репозитория (UserRepositoryCustomплюс реализация с суффиксомImpl), когда нужны подзапросы или сложные выражения; - Querydsl или jOOQ: типобезопасные построители. Основной проект Querydsl долго стоял, живёт форк сообщества; jOOQ генерирует классы по схеме и хорош для тяжёлого SQL;
- Query by Example:
Example.of(probe)для простых форм поиска, но без диапазонов и вложенных условий; @EntityGraph: какие связи подгрузить сразу, не переписывая запрос.
Где спотыкаются
Делают JOIN FETCH по коллекции вместе с Pageable. Hibernate не может ограничить строки в SQL, пишет предупреждение и режет страницу в памяти, выгрузив всю выборку. Решение: сначала страница идентификаторов, потом подгрузка связей.
Склеивают строку нативного запроса из пользовательского ввода. Это SQL-инъекция, значения передаются только через параметры :name или ?1.
Пишут условия спецификаций строками root.get("age"). Опечатка всплывёт только в рантайме, типобезопасный вариант даёт метамодель JPA (User_.age), которую генерирует hibernate-jpamodelgen.