← Назад к вопросам

Какой синтаксис можно использовать с аннотацией 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 только когда необходимо.