如何优化Spring Boot多条件商品搜索接口,轻松扩展筛选器?
商品搜索功能重构方案:实现可扩展的动态筛选
问题现状
当前实现中,为每种筛选组合编写了单独的Repository查询方法和Controller端点,导致代码冗余严重,新增price这类筛选条件时需要创建大量组合方法,扩展性极差。
最优重构方案:使用Spring Data JPA Specification实现动态查询
通过Specification可以动态拼接查询条件,无需为每种组合编写硬编码的查询方法,轻松支持新筛选条件的扩展。
步骤1:修改ProductRepository接口
让Repository继承JpaSpecificationExecutor,获得动态查询能力:
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import com.vaibhavshrivastava.productbrowser.model.Product; import java.util.Optional; public interface ProductRepository extends JpaRepository<Product, Integer>, JpaSpecificationExecutor<Product> { // 仅保留必要的单个查询:根据productCode查询单个商品 Optional<Product> findByProductCode(Integer productCode); }
步骤2:创建Specification构建工具类
编写工具类,根据传入的筛选参数动态构建查询条件:
import org.springframework.data.jpa.domain.Specification; import com.vaibhavshrivastava.productbrowser.model.Product; import jakarta.persistence.criteria.Predicate; import java.util.ArrayList; import java.util.List; public class ProductSpecification { public static Specification<Product> buildSpecification(String name, String brand, Integer productCode, Integer minPrice, Integer maxPrice) { return (root, query, criteriaBuilder) -> { List<Predicate> predicates = new ArrayList<>(); // 名称模糊匹配(参数非空时生效) if (name != null && !name.isEmpty()) { predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%")); } // 品牌精确匹配(参数非空时生效) if (brand != null && !brand.isEmpty()) { predicates.add(criteriaBuilder.equal(root.get("brand"), brand)); } // 商品编码精确匹配(参数非空时生效) if (productCode != null) { predicates.add(criteriaBuilder.equal(root.get("productCode"), productCode)); } // 价格区间筛选(参数非空时生效) if (minPrice != null) { predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("price"), minPrice)); } if (maxPrice != null) { predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("price"), maxPrice)); } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); }; } }
步骤3:简化ProductController
只保留一个通用筛选端点,接收所有可选查询参数:
import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.vaibhavshrivastava.productbrowser.model.Product; import com.vaibhavshrivastava.productbrowser.repository.ProductRepository; import com.vaibhavshrivastava.productbrowser.specification.ProductSpecification; import java.util.List; @RestController @CrossOrigin @RequestMapping("/products") public class ProductController { private final ProductRepository productRepository; // 构造注入替代@Autowired(符合Spring最佳实践) public ProductController(ProductRepository productRepository) { this.productRepository = productRepository; } // 通用筛选端点:支持name、brand、productCode、minPrice、maxPrice等可选参数 @GetMapping public ResponseEntity<List<Product>> getProducts( @RequestParam(required = false) String name, @RequestParam(required = false) String brand, @RequestParam(required = false) Integer productCode, @RequestParam(required = false) Integer minPrice, @RequestParam(required = false) Integer maxPrice) { Specification<Product> spec = ProductSpecification.buildSpecification(name, brand, productCode, minPrice, maxPrice); List<Product> products = productRepository.findAll(spec); return new ResponseEntity<>(products, HttpStatus.OK); } // 保留根据productCode查询单个商品的端点 @GetMapping("/{pid}/details") public ResponseEntity<Product> getProductDetails(@PathVariable("pid") Integer productCode) { Product selectedProduct = productRepository.findByProductCode(productCode).orElseThrow(); return new ResponseEntity<>(selectedProduct, HttpStatus.OK); } }
步骤4:前端Angular传参方式
前端通过查询参数传递筛选条件,示例:
- 按品牌筛选:
GET /products?brand=Apple - 名称模糊搜索+价格区间:
GET /products?name=phone&minPrice=3000&maxPrice=8000 - 多条件组合:
GET /products?name=book&brand=Penguin
扩展新筛选条件的方式
如果需要新增description这类筛选,仅需两步:
- 在
ProductSpecification.buildSpecification方法中添加对应条件:if (description != null && !description.isEmpty()) { predicates.add(criteriaBuilder.like(root.get("description"), "%" + description + "%")); } - 在Controller的
getProducts方法中添加对应的可选@RequestParam参数即可,无需修改其他代码。
其他可选方案
若项目复杂度较高,可选择QueryDSL,它提供类型安全的动态查询API,使用体验更流畅,但需要额外配置依赖和代码生成插件。
内容的提问来源于stack exchange,提问作者Innomight
相关产品推荐
相关产品推荐

