← Назад к вопросам
Можно ли написать SQL запрос в репозитории?
1.8 Middle🔥 181 комментариев
#Docker, Kubernetes и DevOps#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Написание SQL запросов в репозитории
Прямой ответ
Да, можно и это нормальная практика. Репозиторий — это как раз слой для работы с БД, и если стандартные методы CRUD недостаточны, нужно писать собственные запросы.
Когда использовать SQL в репозитории?
1. Сложные запросы, которые невозможно выразить через Query DSL
Например, нужны сложные JOINы, window functions, conditional aggregations:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value =
"SELECT o.id, o.user_id, COUNT(li.id) as items_count, SUM(li.price) as total " +
"FROM orders o " +
"LEFT JOIN order_items li ON o.id = li.order_id " +
"WHERE o.created_at > ?1 " +
"GROUP BY o.id, o.user_id " +
"HAVING SUM(li.price) > ?2 " +
"ORDER BY total DESC",
nativeQuery = true)
List<Object[]> getOrdersWithStats(LocalDate from, BigDecimal minAmount);
}
2. Оптимизация производительности
Устраняем N+1 запросы через FETCH JOIN:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT DISTINCT u FROM User u " +
"LEFT JOIN FETCH u.orders " +
"LEFT JOIN FETCH u.addresses " +
"WHERE u.active = true")
List<User> findAllActiveWithDetails();
}
3. Обновления и удаления по условию
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Modifying
@Query("DELETE FROM Order o WHERE o.status = :status AND o.createdAt < :date")
int deleteOldOrders(@Param("status") OrderStatus status,
@Param("date") LocalDate before);
@Modifying
@Query("UPDATE Order o SET o.status = :newStatus " +
"WHERE o.status = :oldStatus AND o.totalAmount > :amount")
int updateOrderStatusByAmount(@Param("oldStatus") OrderStatus oldStatus,
@Param("newStatus") OrderStatus newStatus,
@Param("amount") BigDecimal minAmount);
}
Подходы к написанию SQL
Вариант 1: @Query с native SQL
@Query(value =
"SELECT u.* FROM users u " +
"WHERE u.status = :status " +
"AND u.created_at >= :from " +
"AND u.created_at < :to",
nativeQuery = true)
List<User> findByStatusAndDateRange(
@Param("status") String status,
@Param("from") LocalDateTime from,
@Param("to") LocalDateTime to);
Вариант 2: @Query с JPQL (типобезопасный)
@Query("SELECT u FROM User u " +
"WHERE u.status = :status " +
"AND u.createdAt >= :from " +
"AND u.createdAt < :to " +
"ORDER BY u.createdAt DESC")
List<User> findByStatusAndDateRange(
@Param("status") UserStatus status,
@Param("from") LocalDateTime from,
@Param("to") LocalDateTime to);
Вариант 3: QueryDSL для типобезопасности
@Repository
public class UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
public List<User> findByStatusAndDate(UserStatus status, LocalDateTime from) {
return queryFactory
.selectFrom(user)
.where(user.status.eq(status)
.and(user.createdAt.goe(from)))
.orderBy(user.createdAt.desc())
.fetch();
}
}
Best Practices
1. Используй именованные параметры
// Хорошо
@Query("SELECT u FROM User u WHERE u.email = :email")
User findByEmail(@Param("email") String email);
2. Не забывай @Modifying для UPDATE/DELETE
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);
3. Используй DTO для сложных результатов
public record OrderStatDTO(
Long orderId,
Integer itemCount,
BigDecimal totalAmount
) {}
@Query(value =
"SELECT new com.example.OrderStatDTO(o.id, COUNT(li), SUM(li.price)) " +
"FROM Order o LEFT JOIN o.items li " +
"GROUP BY o.id")
List<OrderStatDTO> getOrderStats();
4. Документируй сложные запросы
/**
* Находит активные заказы с суммой больше минимума
*/
@Query(value =
"SELECT o.* FROM orders o " +
"WHERE o.status = 'ACTIVE' " +
"AND o.total_amount > :minAmount",
nativeQuery = true)
List<Order> findActiveOrdersByAmount(@Param("minAmount") BigDecimal minAmount);
Когда НЕ писать SQL?
- Простые операции (findById, findAll, save) — используй встроенные методы
- Фильтрация по одному полю — используй method naming convention
User findByEmail(String email);
List<User> findByStatus(UserStatus status);
Выводы
- SQL в репозитории — нормальная практика, это слой работы с БД
- Используй @Query для сложных запросов, которые невозможно выразить через method naming
- Предпочитай JPQL в большинстве случаев — типобезопаснее native SQL
- Native SQL только для специфических функций БД
- Всегда используй @Param вместо позиционных параметров
- Не забывай @Modifying для UPDATE/DELETE операций