Перечисли механизмы для запросов, в которых много параметров
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизмы для запросов с множеством параметров
В 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-инъекций