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

Можно ли написать 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 операций