Ответ
В зависимости от слоя и сложности, использовались несколько подходов:
1. Spring Data JPA с @Query
Для декларативных, статических запросов прямо в репозитории.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// JPQL (объектно-ориентированный)
@Query("SELECT u FROM User u WHERE u.active = true AND u.role = :role")
List<User> findActiveUsersByRole(@Param("role") String role);
// Нативный SQL (для специфичных операций СУБД)
@Query(value = "SELECT * FROM users WHERE last_login < NOW() - INTERVAL '30 days'", nativeQuery = true)
List<User> findInactiveUsers();
}
2. JOOQ для сложных и типобезопасных запросов Использовался в модулях с интенсивной аналитикой.
// Генерация типобезопасного DSL из схемы БД
List<Order> orders = dslContext
.select(ORDERS.ORDER_ID, ORDERS.AMOUNT, CUSTOMERS.NAME)
.from(ORDERS)
.join(CUSTOMERS).on(ORDERS.CUSTOMER_ID.eq(CUSTOMERS.ID))
.where(ORDERS.STATUS.eq("COMPLETED").and(ORDERS.CREATED_AT.gt(LocalDate.now().minusDays(7))))
.fetchInto(Order.class);
3. JDBC PreparedStatement для динамических запросов
Применялся, когда условия запроса формировались в runtime.
StringBuilder sql = new StringBuilder("SELECT * FROM products WHERE 1=1 ");
List<Object> params = new ArrayList<>();
if (category != null) {
sql.append("AND category = ? ");
params.add(category);
}
if (minPrice != null) {
sql.append("AND price >= ? ");
params.add(minPrice);
}
PreparedStatement stmt = connection.prepareStatement(sql.toString());
for (int i = 0; i < params.size(); i++) {
stmt.setObject(i + 1, params.get(i));
}
4. Criteria API / QueryDSL для динамического построения запросов на уровне JPA, избегая конкатенации строк.