You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何降低基于DTO的多字段商品搜索方法的代码复杂度?

Optimized DTO-Based Product Search Implementation for MongoDB

Looking at your code, the main pain points are repetitive null checks for the Criteria object and duplicated logic across each search field. Let's refactor this to make it cleaner, more maintainable, and less error-prone.

Key Issues in the Original Code:

  • Redundant null checks (criteria != null ? ... : ...) cluttering every field's condition
  • Duplicate regex/exact-match logic repeated across multiple fields
  • Unnecessary re-assignment of Pageable when building the query
  • Potential bug: Primitive numeric types (like int for price/stock) can't distinguish between a user searching for 0 vs. the default "not set" state

Optimized Solution:

We'll extract repetitive criteria-building logic into helper methods, eliminate redundant checks, and streamline query construction:

import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.stream.Collectors;

public class ProductService {

    private static final String CASE_INSENSITIVE_FLAG = "i";
    private final ProductMapper productMapper;
    private final MongoTemplate mongoTemplate;

    // Constructor with dependency injection
    public ProductService(ProductMapper productMapper, MongoTemplate mongoTemplate) {
        this.productMapper = productMapper;
        this.mongoTemplate = mongoTemplate;
    }

    public List<ProductDTO> search(SearchProductDTO productDTO) {
        Criteria criteria = null;

        // Add case-insensitive regex criteria for string fields
        criteria = appendRegexCriteria(criteria, NAME.getLabel(), productDTO.getName());
        criteria = appendRegexCriteria(criteria, DESCRIPTION.getLabel(), productDTO.getDescription());
        criteria = appendRegexCriteria(criteria, BRAND.getLabel(), productDTO.getBrand());
        criteria = appendRegexCriteria(criteria, TYPE.getLabel(), productDTO.getType());
        criteria = appendRegexCriteria(criteria, COLOR.getLabel(), productDTO.getColor());

        // Add exact-match criteria for non-string fields
        criteria = appendExactMatchCriteria(criteria, PRICE.getLabel(), productDTO.getPrice(), 0);
        criteria = appendExactMatchCriteria(criteria, STOCK_COUNT.getLabel(), productDTO.getStockCount(), 0);
        criteria = appendExactMatchCriteria(criteria, SIZE.getLabel(), productDTO.getProductSize(), null);
        criteria = appendExactMatchCriteria(criteria, GENDER.getLabel(), productDTO.getGender(), null);

        // Build query with pagination
        Pageable pageable = PageRequest.of(productDTO.getPage(), productDTO.getSize());
        Query query = new Query(pageable);
        
        if (criteria != null) {
            query.addCriteria(criteria);
        }

        // Fetch and map results to DTOs
        List<Product> products = mongoTemplate.find(query, Product.class);
        return products.stream()
                .map(productMapper::modelToDto)
                .collect(Collectors.toList());
    }

    /**
     * Helper to append case-insensitive regex criteria if the value is not blank
     */
    private Criteria appendRegexCriteria(Criteria currentCriteria, String fieldName, String value) {
        if (StringUtils.isNotBlank(value)) {
            return currentCriteria == null 
                ? Criteria.where(fieldName).regex(value, CASE_INSENSITIVE_FLAG) 
                : currentCriteria.and(fieldName).regex(value, CASE_INSENSITIVE_FLAG);
        }
        return currentCriteria;
    }

    /**
     * Helper to append exact-match criteria if the value is not the default
     * Works for both primitive types (using default value) and reference types (checking for null)
     */
    private <T> Criteria appendExactMatchCriteria(Criteria currentCriteria, String fieldName, T value, T defaultValue) {
        if (value != null && !value.equals(defaultValue)) {
            return currentCriteria == null 
                ? Criteria.where(fieldName).is(value) 
                : currentCriteria.and(fieldName).is(value);
        }
        return currentCriteria;
    }
}

Improvements Explained:

  1. Helper Methods: Encapsulates repetitive null checks and condition logic, making the main search method concise and easy to scan.
  2. Readability: Each search field's criteria is added in a single line, making it simple to modify or add new fields later.
  3. Reduced Redundancy: Eliminates duplicated ternary operators and unnecessary Pageable re-assignments.
  4. Maintainability: Adding a new search field only requires one line in the main method, no need to rewrite null-check logic.

Further Recommendations:

  • Use Wrapper Types for Numerics: Change price and stockCount in SearchProductDTO to Integer/Double instead of primitives. This lets you use null to indicate "not set" instead of relying on 0, fixing the bug where users can't search for items with price=0 or stock=0.
  • Add DTO Validation: Validate pagination parameters (e.g., page number ≥0, size ≤50) to prevent invalid requests.
  • Consider Querydsl: If you're open to adding a dependency, Spring Data MongoDB's Querydsl integration enables type-safe query building with even less boilerplate.

内容的提问来源于stack exchange,提问作者ElbrusGarayev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 21:32:52