Комментарии (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 параметрами.