Свои запросы: @Query
Запрос по имени метода хорош, пока условие простое. Поиск слова сразу в заголовке и тексте превратился бы в findByTitleContainingIgnoreCaseOrBodyContainingIgnoreCase — читать такое трудно. В этом случае запрос пишут сами в аннотации @Query, а имя метода выбирают любое.
JPQL
// src/main/java/com/example/blog/PostRepository.java
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("""
select p from Post p
where lower(p.title) like lower(concat('%', :word, '%'))
or lower(p.body) like lower(concat('%', :word, '%'))
""")
List<Post> search(@Param("word") String word);
}Это JPQL — язык запросов JPA. Он похож на SQL, но работает с сущностями и их полями: Post — имя класса, а не таблицы, p.title — поле, а не столбец. :word — именованный параметр, его значение берётся из аргумента с @Param("word"). Текстовый блок """ позволяет записать запрос в несколько строк.
Hibernate переведёт JPQL в SQL для вашей базы (вывод сокращён):
Hibernate: select ... from posts p1_0 where lower(p1_0.title) like lower(('%'||?||'%')) escape '' or lower(p1_0.body) like lower(('%'||?||'%')) escape ''Использование
В сервисе метод search теперь вызывает новый запрос:
// src/main/java/com/example/blog/PostService.java
public List<PostResponse> search(String word) {
return posts.search(word).stream().map(PostResponse::from).toList();
}Запрос curl "http://localhost:8080/api/posts/search?q=репозитории" найдёт статью «Spring Data JPA», хотя слово есть только в её тексте.
Обычный SQL
Если нужна особенность конкретной базы, можно написать настоящий SQL — с nativeQuery = true:
// src/main/java/com/example/blog/PostRepository.java
@Query(value = "select * from posts order by created_at desc limit :count", nativeQuery = true)
List<Post> findLatest(@Param("count") int count);Здесь уже имена таблицы и столбцов: posts, created_at. Такой запрос Hibernate не переводит, поэтому при смене базы его, возможно, придётся переписать.
Что выбрать
| Способ | Когда |
|---|---|
| Имя метода | Одно-два простых условия |
@Query на JPQL | Сложные условия, соединения, подсчёты |
@Query с nativeQuery | Возможности конкретной базы, которых нет в JPQL |
Изменяющие запросы
Для update и delete к @Query добавляют @Modifying, а вызывающий метод сервиса должен работать в транзакции (о них — через урок):
@Modifying
@Query("delete from Post p where p.createdAt < :before")
int deleteOlderThan(@Param("before") Instant before);Метод вернёт число удалённых строк.
Частая ошибка
В JPQL пишут имена классов и полей. Если по привычке написать имя таблицы — select p from posts p, — приложение не запустится: Hibernate проверяет запросы при старте и сообщит, что сущность posts ему неизвестна. Для JPQL нужен Post, для nativeQuery — posts.