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

Можно ли написать свой запрос в JPA?

1.3 Junior🔥 151 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли написать свой запрос в JPA?

Краткий ответ

Да, можно и нужно! JPA предоставляет несколько способов писать кастомные запросы (custom queries) помимо стандартных методов findById(), findAll() и т.д.

Способы написания кастомных запросов в JPA

1. @Query с JPQL

JPQL (Java Persistence Query Language) — это объектно-ориентированный язык запросов, работающий с сущностями, а не с таблицами.

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmail(@Param("email") String email);
    
    @Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName")
    User findByFullName(@Param("firstName") String firstName, @Param("lastName") String lastName);
    
    @Query("SELECT u FROM User u JOIN u.roles r WHERE r.name = :roleName")
    List<User> findUsersByRole(@Param("roleName") String roleName);
}

2. @Query с Native SQL

Для сложных запросов используй Native SQL:

public interface UserRepository extends JpaRepository<User, Long> {
    @Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
    User findByEmailNative(@Param("email") String email);
}

3. Query by Method Name

Spring Data JPA генерирует запрос по имени метода:

public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
    List<User> findByFirstName(String firstName);
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    List<User> findByEmailContaining(String emailPart);
    List<User> findByAgeGreaterThan(int age);
    List<User> findTop10ByActiveIsTrue();
}

4. QueryDSL для типобезопасных запросов

public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public List<User> searchUsers(String email, Integer minAge) {
        QUser user = QUser.user;
        BooleanBuilder builder = new BooleanBuilder();
        if (email != null) {
            builder.and(user.email.contains(email));
        }
        if (minAge != null) {
            builder.and(user.age.goe(minAge));
        }
        return (List<User>) userRepository.findAll(builder);
    }
}

5. Criteria API для динамических запросов

@Service
public class UserService {
    private final EntityManager entityManager;
    
    public List<User> findUsersDynamically(String firstName, Integer minAge, String email) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> user = query.from(User.class);
        
        List<Predicate> predicates = new ArrayList<>();
        if (firstName != null) predicates.add(cb.equal(user.get("firstName"), firstName));
        if (minAge != null) predicates.add(cb.ge(user.get("age"), minAge));
        if (email != null) predicates.add(cb.like(user.get("email"), "%" + email + "%"));
        
        query.where(cb.and(predicates.toArray(new Predicate[0])));
        return entityManager.createQuery(query).getResultList();
    }
}

Сравнение подходов

СпособПлюсыМинусы
Query by Method NameПросто, читаемоТолько простые условия
@Query JPQLТипобезопасно, портативноМеньше контроля
@Query Native SQLПолный контрольЗависит от БД
QueryDSLТипобезопасно, динамичноДоп. зависимость
Criteria APIТипобезопасно, встроеноМногословно

Практические примеры

Пример 1: Поиск с пагинацией

public interface ProductRepository extends JpaRepository<Product, Long> {
    @Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice")
    Page<Product> findByPriceRange(
        @Param("minPrice") BigDecimal minPrice,
        @Param("maxPrice") BigDecimal maxPrice,
        Pageable pageable
    );
}

Пример 2: Агрегирующий запрос

public interface OrderRepository extends JpaRepository<Order, Long> {
    @Query("SELECT new map(u.id as userId, u.email as email, COUNT(o.id) as totalOrders) FROM Order o JOIN o.user u GROUP BY u.id, u.email")
    List<Map<String, Object>> getUserOrdersStatistics();
}

Пример 3: Запрос с подзапросом

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.id IN (SELECT o.user.id FROM Order o WHERE o.createdAt > :date) AND u.status = 'ACTIVE'")
    List<User> findActiveUsersWithRecentOrders(@Param("date") LocalDateTime date);
}

Защита от SQL Injection

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmail(@Param("email") String email);
}

Проблема N+1

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.roles")
    List<User> findAllWithRoles();
    
    @EntityGraph(attributePaths = {"roles", "permissions"})
    List<User> findAllWithAssociations();
}

Когда писать свой запрос

Используй @Query когда нужны JOIN, GROUP BY, агрегирующие функции или специфика БД. Не используй когда findByXxx() справится. Всегда защищай от SQL Injection параметрами.

Можно ли написать свой запрос в JPA? | PrepBro