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

Перечисли механизмы для запросов, в которых много параметров

2.0 Middle🔥 231 комментариев
#Docker, Kubernetes и DevOps#REST API и микросервисы

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

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

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

Механизмы для запросов с множеством параметров

В Java разработке, особенно при работе с REST API и базами данных, часто возникает необходимость обработки запросов со множеством параметров. Существует несколько проверенных механизмов и паттернов для решения этой задачи.

1. Builder паттерн (BuilderPattern)

Builder — один из самых популярных паттернов для работы с объектами, содержащими много параметров:

public class SearchRequest {
    private String query;
    private Integer pageSize;
    private Integer pageNumber;
    private LocalDate startDate;
    private LocalDate endDate;
    private String sortBy;
    private Boolean ascending;
    private List<String> categories;
    private String language;
    
    // Constructor private
    private SearchRequest(Builder builder) {
        this.query = builder.query;
        this.pageSize = builder.pageSize;
        this.pageNumber = builder.pageNumber;
        this.startDate = builder.startDate;
        this.endDate = builder.endDate;
        this.sortBy = builder.sortBy;
        this.ascending = builder.ascending;
        this.categories = builder.categories;
        this.language = builder.language;
    }
    
    public static class Builder {
        private String query;
        private Integer pageSize = 10;
        private Integer pageNumber = 1;
        private LocalDate startDate;
        private LocalDate endDate;
        private String sortBy = "relevance";
        private Boolean ascending = false;
        private List<String> categories = new ArrayList<>();
        private String language = "en";
        
        public Builder query(String query) {
            this.query = query;
            return this;
        }
        
        public Builder pageSize(Integer pageSize) {
            this.pageSize = pageSize;
            return this;
        }
        
        public Builder pageNumber(Integer pageNumber) {
            this.pageNumber = pageNumber;
            return this;
        }
        
        public Builder startDate(LocalDate startDate) {
            this.startDate = startDate;
            return this;
        }
        
        public Builder endDate(LocalDate endDate) {
            this.endDate = endDate;
            return this;
        }
        
        public Builder sortBy(String sortBy) {
            this.sortBy = sortBy;
            return this;
        }
        
        public Builder ascending(Boolean ascending) {
            this.ascending = ascending;
            return this;
        }
        
        public Builder categories(List<String> categories) {
            this.categories = categories;
            return this;
        }
        
        public Builder language(String language) {
            this.language = language;
            return this;
        }
        
        public SearchRequest build() {
            return new SearchRequest(this);
        }
    }
}

// Использование
SearchRequest request = new SearchRequest.Builder()
    .query("java tutorial")
    .pageSize(20)
    .pageNumber(2)
    .startDate(LocalDate.of(2024, 1, 1))
    .sortBy("date")
    .ascending(true)
    .build();

2. DTO (Data Transfer Object) с аннотациями валидации

Используется для передачи данных между слоями приложения:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class FilterDto {
    @NotBlank(message = "Query cannot be blank")
    private String query;
    
    @Min(1)
    @Max(100)
    private Integer pageSize = 10;
    
    @Min(1)
    private Integer pageNumber = 1;
    
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate startDate;
    
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate endDate;
    
    @Pattern(regexp = "relevance|date|rating")
    private String sortBy = "relevance";
    
    private List<String> categories;
    
    @Length(min = 2, max = 5)
    private String language = "en";
}

// В контроллере
@GetMapping("/search")
public ResponseEntity<List<SearchResult>> search(
    @Valid @ModelAttribute FilterDto filter,
    BindingResult bindingResult
) {
    if (bindingResult.hasErrors()) {
        throw new ValidationException(bindingResult.getFieldErrors());
    }
    return ResponseEntity.ok(searchService.search(filter));
}

3. Query Object паттерн

Используется для сложных запросов к БД с динамическими условиями:

public class UserQuery {
    private String firstName;
    private String lastName;
    private Integer minAge;
    private Integer maxAge;
    private String city;
    private List<String> roles;
    private Boolean active;
    
    public Specification<User> toSpecification() {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if (firstName != null && !firstName.isEmpty()) {
                predicates.add(criteriaBuilder.like(
                    root.get("firstName"),
                    "%" + firstName + "%"
                ));
            }
            
            if (lastName != null && !lastName.isEmpty()) {
                predicates.add(criteriaBuilder.like(
                    root.get("lastName"),
                    "%" + lastName + "%"
                ));
            }
            
            if (minAge != null) {
                predicates.add(criteriaBuilder.ge(
                    root.get("age"), minAge
                ));
            }
            
            if (maxAge != null) {
                predicates.add(criteriaBuilder.le(
                    root.get("age"), maxAge
                ));
            }
            
            if (city != null && !city.isEmpty()) {
                predicates.add(criteriaBuilder.equal(
                    root.get("city"), city
                ));
            }
            
            if (roles != null && !roles.isEmpty()) {
                predicates.add(root.get("roles").in(roles));
            }
            
            if (active != null) {
                predicates.add(criteriaBuilder.equal(
                    root.get("active"), active
                ));
            }
            
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }
}

// Использование
@GetMapping("/users")
public List<User> getUsers(
    @ModelAttribute UserQuery query,
    Pageable pageable
) {
    return userRepository.findAll(query.toSpecification(), pageable).getContent();
}

4. Параметризованные SQL запросы (подготовленные выражения)

Для защиты от SQL-инъекций при работе с БД:

public List<Order> searchOrders(
    String customerId,
    LocalDate startDate,
    LocalDate endDate,
    String status,
    BigDecimal minAmount
) {
    String sql = "SELECT * FROM orders WHERE 1=1";
    List<Object> params = new ArrayList<>();
    
    if (customerId != null) {
        sql += " AND customer_id = ?";
        params.add(customerId);
    }
    
    if (startDate != null) {
        sql += " AND order_date >= ?";
        params.add(startDate);
    }
    
    if (endDate != null) {
        sql += " AND order_date <= ?";
        params.add(endDate);
    }
    
    if (status != null) {
        sql += " AND status = ?";
        params.add(status);
    }
    
    if (minAmount != null) {
        sql += " AND total_amount >= ?";
        params.add(minAmount);
    }
    
    sql += " ORDER BY order_date DESC";
    
    try (Connection conn = dataSource.getConnection();
         PreparedStatement stmt = conn.prepareStatement(sql)) {
        
        for (int i = 0; i < params.size(); i++) {
            stmt.setObject(i + 1, params.get(i));
        }
        
        ResultSet rs = stmt.executeQuery();
        // Обработка результатов
    }
}

5. Criteria API (JPA)

Динамическое построение запросов с использованием Criteria API:

public List<Product> findProducts(
    String category,
    BigDecimal minPrice,
    BigDecimal maxPrice,
    Boolean inStock,
    String sortBy
) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Product> query = cb.createQuery(Product.class);
    Root<Product> root = query.from(Product.class);
    
    List<Predicate> predicates = new ArrayList<>();
    
    if (category != null) {
        predicates.add(cb.equal(root.get("category"), category));
    }
    
    if (minPrice != null) {
        predicates.add(cb.ge(root.get("price"), minPrice));
    }
    
    if (maxPrice != null) {
        predicates.add(cb.le(root.get("price"), maxPrice));
    }
    
    if (inStock != null) {
        predicates.add(cb.equal(root.get("inStock"), inStock));
    }
    
    if (!predicates.isEmpty()) {
        query.where(cb.and(predicates.toArray(new Predicate[0])));
    }
    
    if (sortBy != null && sortBy.equals("price_asc")) {
        query.orderBy(cb.asc(root.get("price")));
    } else if (sortBy != null && sortBy.equals("price_desc")) {
        query.orderBy(cb.desc(root.get("price")));
    }
    
    return entityManager.createQuery(query).getResultList();
}

6. MapStruct для преобразования параметров

Упрощает преобразование DTOs в сложные объекты:

@Mapper
public interface SearchRequestMapper {
    SearchRequest dtoToRequest(SearchFilterDto dto);
    
    @Mapping(target = "pageSize", defaultValue = "10")
    @Mapping(target = "pageNumber", defaultValue = "1")
    SearchRequest mapWithDefaults(SearchFilterDto dto);
}

// Использование в контроллере
@RestController
public class SearchController {
    @Autowired
    private SearchRequestMapper mapper;
    
    @GetMapping("/search")
    public ResponseEntity<?> search(@ModelAttribute SearchFilterDto filter) {
        SearchRequest request = mapper.dtoToRequest(filter);
        return ResponseEntity.ok(searchService.execute(request));
    }
}

7. URL Query Parameters с использованием @RequestParam

Стандартный способ передачи параметров в REST API:

@GetMapping("/filter")
public ResponseEntity<List<Item>> filterItems(
    @RequestParam(required = false) String name,
    @RequestParam(required = false) String category,
    @RequestParam(defaultValue = "0") Integer page,
    @RequestParam(defaultValue = "10") Integer size,
    @RequestParam(defaultValue = "name") String sortBy,
    @RequestParam(defaultValue = "ASC") Sort.Direction direction
) {
    Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
    List<Item> items = itemService.filter(name, category, pageable);
    return ResponseEntity.ok(items);
}

Лучшие практики

1. Валидация на границе приложения

  • Используй @Valid и ConstraintValidator для проверки корректности параметров
  • Обрабатывай ValidationException в глобальном ExceptionHandler

2. Документирование параметров

  • Используй Swagger/OpenAPI для документации всех параметров
  • Указывай обязательные и опциональные параметры

3. Безопасность

  • Всегда используй подготовленные выражения (PreparedStatement)
  • Не передавай SQL код напрямую в параметрах
  • Санитизируй строковые параметры

4. Производительность

  • Используй индексы для часто фильтруемых полей
  • Кешируй результаты частых запросов
  • Используй pagination для больших результатов

5. Читаемость кода

  • Предпочитай Builder для объектов с множеством параметров
  • Используй понятные имена параметров
  • Группируй связанные параметры в классы

Итоговый чеклист

✓ Выбран подходящий паттерн для вашего случая ✓ Все параметры имеют значения по умолчанию где необходимо ✓ Реализована валидация входных данных ✓ Параметры документированы ✓ Используются подготовленные выражения для SQL ✓ Код защищён от SQL-инъекций

Перечисли механизмы для запросов, в которых много параметров | PrepBro