← Назад к вопросам
Какой синтаксис можно использовать с аннотацией Query в Spring Data JPA?
2.0 Middle🔥 161 комментариев
#Spring Boot и Spring Data
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Аннотация @Query в Spring Data JPA
@Query — это аннотация в Spring Data JPA, которая позволяет писать кастомные JPQL, SQL или Criteria Query'и напрямую в методах Repository. Это мощный инструмент для реализации сложных запросов.
1. JPQL (Java Persistence Query Language) — синтаксис по умолчанию
JPQL — это язык запросов, похожий на SQL, но работает с сущностями, а не таблицами.
public interface UserRepository extends JpaRepository<User, Long> {
// Простой SELECT
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
// SELECT с WHERE и AND
@Query("SELECT u FROM User u WHERE u.username = ?1 AND u.isActive = true")
User findActiveByUsername(String username);
// SELECT с LIKE
@Query("SELECT u FROM User u WHERE u.email LIKE %?1%")
List<User> findEmailContaining(String pattern);
// Подсчёт
@Query("SELECT COUNT(u) FROM User u WHERE u.isActive = true")
long countActiveUsers();
// SELECT с JOIN
@Query("SELECT u FROM User u JOIN u.orders o WHERE o.status = 'COMPLETED'")
List<User> findUsersWithCompletedOrders();
}
2. Параметризованные запросы (защита от SQL injection)
Вариант 1: Позиционные параметры (?N)
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.status = ?2")
User findByEmailAndStatus(String email, String status);
Вариант 2: Именованные параметры (@Param) — РЕКОМЕНДУЕТСЯ
@Query("SELECT u FROM User u WHERE u.email = :email AND u.status = :status")
User findByEmailAndStatus(
@Param("email") String email,
@Param("status") String status
);
Преимущества именованных параметров:
- Более читаемо
- Безопаснее (меньше ошибок с порядком)
- Легче рефакторить
// ✅ Правильно
@Query("SELECT u FROM User u WHERE u.id IN :ids")
List<User> findByIds(@Param("ids") List<Long> ids);
// ❌ Неправильно (легко спутать параметры)
@Query("SELECT u FROM User u WHERE u.id = ?1 AND u.name = ?2")
User findByIdAndName(Long id, String name); // Какой параметр на каком месте?
3. Native SQL Query (nativeQuery = true)
Для запросов, специфичных для конкретной БД (PostgreSQL, MySQL и т.д.):
public interface UserRepository extends JpaRepository<User, Long> {
// Native SQL
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);
// Native SQL с именованными параметрами
@Query(value = "SELECT * FROM users WHERE email = :email AND age > :minAge",
nativeQuery = true)
List<User> findAdultsByEmail(
@Param("email") String email,
@Param("minAge") int minAge
);
// PostgreSQL специфичный синтаксис
@Query(value = "SELECT * FROM users WHERE LOWER(email) = LOWER(:email)",
nativeQuery = true)
User findByEmailCaseInsensitive(@Param("email") String email);
}
Когда использовать Native SQL:
- Сложные запросы с специальными функциями БД
- Window functions (OVER, ROW_NUMBER и т.д.)
- JSON операции в PostgreSQL
- Оптимизация производительности
4. @Modifying для INSERT/UPDATE/DELETE
Для изменения данных нужна аннотация @Modifying + @Transactional:
public interface UserRepository extends JpaRepository<User, Long> {
// UPDATE
@Modifying
@Transactional
@Query("UPDATE User u SET u.isActive = true WHERE u.id = :id")
void activateUser(@Param("id") Long id);
// DELETE
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.isActive = false")
int deleteInactiveUsers();
// Bulk UPDATE с условием
@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :newStatus WHERE u.status = :oldStatus")
int updateUserStatus(
@Param("oldStatus") String oldStatus,
@Param("newStatus") String newStatus
);
// Native SQL UPDATE
@Modifying
@Transactional
@Query(value = "UPDATE users SET last_login = NOW() WHERE id = :id",
nativeQuery = true)
void updateLastLogin(@Param("id") Long id);
}
5. Возвращаемые типы
public interface UserRepository extends JpaRepository<User, Long> {
// Один объект
@Query("SELECT u FROM User u WHERE u.id = :id")
User findById(@Param("id") Long id);
// Optional
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
// List
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findByStatus(@Param("status") String status);
// Projection (выбрать только нужные поля)
@Query("SELECT new map(u.id as id, u.email as email, u.username as username) FROM User u")
List<Map<String, Object>> findUsersMapped();
// Stream для больших результатов
@Query("SELECT u FROM User u WHERE u.isActive = true")
Stream<User> findActiveUsersAsStream();
// Pageable
@Query("SELECT u FROM User u WHERE u.isActive = true")
Page<User> findActiveUsers(Pageable pageable);
// Custom DTO
@Query("SELECT new com.example.dto.UserDTO(u.id, u.email, u.username) FROM User u")
List<UserDTO> findAllAsDTO();
}
6. Сложные запросы с JOIN и agregация
public interface OrderRepository extends JpaRepository<Order, Long> {
// LEFT JOIN
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.items WHERE o.id = :id")
Order findOrderWithItems(@Param("id") Long id);
// GROUP BY и HAVING
@Query("SELECT u.username, COUNT(o.id) as orderCount FROM User u LEFT JOIN u.orders o GROUP BY u.username HAVING COUNT(o.id) > :minOrders")
List<Object[]> findUsersWithMinOrderCount(@Param("minOrders") long minOrders);
// Subquery
@Query("SELECT u FROM User u WHERE u.id IN (SELECT DISTINCT o.user.id FROM Order o WHERE o.status = 'COMPLETED')")
List<User> findUsersWithCompletedOrders();
// ORDER BY и LIMIT
@Query(value = "SELECT * FROM orders ORDER BY created_date DESC LIMIT :limit", nativeQuery = true)
List<Order> findRecentOrders(@Param("limit") int limit);
}
7. Пример реального использования
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Поиск пользователей по email (JPQL)
@Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)")
Optional<User> findByEmailIgnoreCase(@Param("email") String email);
// Поиск активных пользователей с их количеством заказов (JOIN + GROUP BY)
@Query("""
SELECT u, COUNT(o.id) as orderCount
FROM User u
LEFT JOIN u.orders o
WHERE u.isActive = true
GROUP BY u.id
ORDER BY orderCount DESC
""")
List<Object[]> findActiveUsersWithOrderCount();
// Обновление последнего входа (Native SQL)
@Modifying
@Transactional
@Query(value = "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = :id",
nativeQuery = true)
void updateLastLogin(@Param("id") Long id);
// Удаление неактивных пользователей
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.isActive = false AND u.lastLogin < :cutoffDate")
int deleteInactiveUsers(@Param("cutoffDate") LocalDateTime cutoffDate);
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User authenticateUser(String email, String password) {
return userRepository.findByEmailIgnoreCase(email)
.filter(u -> passwordEncoder.matches(password, u.getPassword()))
.orElseThrow(() -> new AuthenticationException("Invalid credentials"));
}
}
Best Practices
// ✅ Правильно: используй именованные параметры
@Query("SELECT u FROM User u WHERE u.email = :email AND u.status = :status")
User find(@Param("email") String email, @Param("status") String status);
// ❌ Неправильно: позиционные параметры сложнее читать
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.status = ?2")
User find(String email, String status);
// ✅ Правильно: для массовых изменений
@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
void updateStatuses(@Param("ids") List<Long> ids, @Param("status") String status);
// ❌ Неправильно: обновление в цикле
for (Long id : ids) {
userRepository.updateStatus(id, status); // N+1 запросов
}
// ✅ Правильно: используй Stream для больших результатов
@Query("SELECT u FROM User u WHERE u.isActive = true")
Stream<User> findActiveUsers();
// ❌ Неправильно: загрузить все в память
@Query("SELECT u FROM User u WHERE u.isActive = true")
List<User> findActiveUsers(); // OutOfMemoryError для большого кол-ва
Итого
@Query поддерживает несколько синтаксисов:
- JPQL (по умолчанию) — для портативных запросов
- Native SQL — для специфичных запросов БД
- Параметры: позиционные (?1) или именованные (:name)
- @Modifying — для INSERT/UPDATE/DELETE
- Различные возвращаемые типы: List, Optional, Page, Stream, Map
Рекомендация: используй именованные параметры и JPQL по умолчанию, Native SQL только когда необходимо.