Spring Boot结合Spring Data JPA如何处理多可选请求参数过滤查询?
Spring Boot + Spring Data JPA 多参数组合动态过滤解决方案
首先明确:不需要为每种参数组合单独编写查询语句,Spring Data JPA提供了多种动态查询能力,可以灵活适配任意参数组合的过滤需求,下面给出3种常用实现方案:
方案1:JPA Specification(无额外依赖,灵活度最高)
这是Spring Data JPA原生支持的动态查询方案,无需引入第三方依赖,支持等值、模糊、范围等任意查询条件:
- 改造Repository接口,继承
JpaSpecificationExecutor
public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> { }
- 编写动态条件构建逻辑
在Service层根据前端传入的非空参数,动态拼接查询条件:
import org.springframework.data.jpa.domain.Specification; import jakarta.persistence.criteria.Predicate; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; public Specification<Employee> buildFilterSpec(Long id, String firstName, String lastName, String designaton, Integer age, BigDecimal salary) { return (root, query, criteriaBuilder) -> { List<Predicate> predicates = new ArrayList<>(); // 参数非空则加入查询条件 if (id != null) { predicates.add(criteriaBuilder.equal(root.get("id"), id)); } if (firstName != null && !firstName.isBlank()) { predicates.add(criteriaBuilder.like(root.get("firstName"), "%" + firstName + "%")); } if (lastName != null && !lastName.isBlank()) { predicates.add(criteriaBuilder.equal(root.get("lastName"), lastName)); } if (designaton != null && !designaton.isBlank()) { predicates.add(criteriaBuilder.equal(root.get("designaton"), designaton)); } if (age != null) { // 可根据需求调整为大于/小于/区间查询 predicates.add(criteriaBuilder.equal(root.get("age"), age)); } if (salary != null) { predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("salary"), salary)); } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); }; }
- 执行查询
直接调用Repository自带的findAll方法传入构造好的Specification即可:
Specification<Employee> spec = buildFilterSpec(id, firstName, lastName, designaton, age, salary); // 支持分页、排序,传入Pageable参数即可 List<Employee> result = employeeRepository.findAll(spec);
方案2:Query By Example(QBE,代码最简,适合等值匹配场景)
如果你的过滤需求都是等值匹配,没有范围查询、复杂模糊匹配的要求,可以用QBE方案,无需手动判断参数非空:
// 把前端传入的参数直接赋值给Employee实体对象 Employee filterParam = new Employee(); filterParam.setFirstName(firstName); filterParam.setLastName(lastName); filterParam.setAge(age); filterParam.setDesignaton(designaton); // 配置匹配规则 ExampleMatcher matcher = ExampleMatcher.matching() .withIgnoreCase() // 忽略大小写 .withIgnoreNullValues(); // 自动忽略null值字段 Example<Employee> example = Example.of(filterParam, matcher); // 执行查询 List<Employee> result = employeeRepository.findAll(example);
方案3:QueryDSL(类型安全,避免硬编码字段名)
如果项目允许引入额外依赖,QueryDSL可以避免Specification方案中硬写字段名的问题,编译期就能检查字段正确性:
- 引入QueryDSL相关依赖并配置生成查询实体类
- 构建动态查询条件:
QEmployee qEmployee = QEmployee.employee; BooleanBuilder condition = new BooleanBuilder(); if (firstName != null && !firstName.isBlank()) { condition.and(qEmployee.firstName.contains(firstName)); } if (age != null) { condition.and(qEmployee.age.eq(age)); } if (salary != null) { condition.and(qEmployee.salary.goe(salary)); } // 其他参数同理 // 执行查询 List<Employee> result = employeeRepository.findAll(condition);
注意事项
- 接口层所有
@RequestParam都要设置required = false,且数值类型用包装类(比如Integer、BigDecimal),避免基础类型默认值干扰非空判断 - 三种方案都原生支持分页、排序需求,只需要在调用
findAll方法时传入Pageable参数即可
内容的提问来源于stack exchange,提问作者Umang Kamdar
相关产品推荐
相关产品推荐

