Spring Boot 用一个接口搞定所有查询

B站影视 港台电影 2025-06-04 08:46 2

摘要:如果你用过 Spring Boot,一定很熟悉“铁三角”:@Controller、@Service 和 @Repository。每加一个实体,通常就会有对应的 Controller 来处理 CRUD 和业务逻辑——至少对于同步操作来说如此。

如果你用过 Spring Boot,一定很熟悉“铁三角”:@Controller、@Service 和 @Repository。每加一个实体,通常就会有对应的 Controller 来处理 CRUD 和业务逻辑——至少对于同步操作来说如此。

创建 POST、PUT、DELETE 这些接口很简单:校验、业务逻辑一接,接口就能用了。

但 GET 呢?

来看一个简单的 Employee 实体:

public class Employee extends BaseUUIDEntity { private Long id; private String name; private String email; private String mobile; private String role; private String status; private String address;}

现在我们要查所有指定状态的员工,很简单:

@GetMappingpublic List getEmployeesByStatus(@RequestParam String status) { return employeeRepository.findByStatus(status);}

清爽利落,对吧?

@GetMappingpublic List getEmployeesByStatusAndRole(@RequestParam String status, @RequestParam String role) { return employeeRepository.findByStatusAndRole(status, role);}

又一次上线、又一次版本号递增。

然后又有同事说:

“我们想按 status 或 role 查询,还能不能加个 department 过滤?”

糟了,问题来了:组合太多,维护太累。

Spring Specification 提供了一种动态、可复用的方式,用 JPA Criteria API 构建查询。

可以把 Specification 理解为运行时构建 Predicate 的工具,让你对查询条件有极高的灵活性。

我们来用 Specification 搭一个灵活的查询解析器。

public Specification parseSearchparams(MultiValueMap params) { return (root, query, criteriaBuilder) -> { List predicates = new ArrayList; for (Map.Entry> param : params.entrySet) { String key = param.getKey; List values = param.getValue; if (key.equals("page") || key.equals("size") || key.equals("sort")) { continue; // 跳过分页/排序参数 } try { Path path = root.get(key); if (values.get(0).startsWith("in:")) { String valuesArray = values.get(0).substring(3).split(","); List trimmedValues = Arrays.stream(valuesArray) .map(String::trim) .collect(Collectors.toList); predicates.add(path.in(trimmedValues)); continue; } for (String value : values) { if (value.startsWith("eq:")) { predicates.add(criteriaBuilder.equal(path, value.substring(3))); } else if (value.startsWith("like:")) { predicates.add(criteriaBuilder.like(path.as(String.class), "%" + value.substring(5) + "%")); } else if (value.startsWith("gt:")) { predicates.add(criteriaBuilder.greaterThan(path.as(String.class), value.substring(3))); } else if (value.startsWith("gte:")) { predicates.add(criteriaBuilder.greaterThanOrEqualTo(path.as(String.class), value.substring(4))); } else if (value.startsWith("lt:")) { predicates.add(criteriaBuilder.lessThan(path.as(String.class), value.substring(3))); } else if (value.startsWith("lte:")) { predicates.add(criteriaBuilder.lessThanOrEqualTo(path.as(String.class), value.substring(4))); } } } catch (Exception e) { log.error("创建 {}:{} 的谓词出错", key, values, e); } } return predicates.isEmpty ? criteriaBuilder.conjunction : criteriaBuilder.and(predicates.toArray(new Predicate[0])); };}@GetMappingpublic List getEmployees(@RequestParam MultiValueMap params) { Specification spec = parseSearchParams(params); return employeeRepository.findAll(spec);}# 查找所有在职员工curl -X GET "http://localhost:8080/api/employees?status=eq:ACTIVE"# 查找所有角色包含 manager 的在职员工curl -X GET "http://localhost:8080/api/employees?status=eq:ACTIVE&role=like:manager"# 查找指定角色的员工curl -X GET "http://localhost:8080/api/employees?role=in:Manager,Engineer"

这种模式可以轻松适配任何实体,让你的代码库保持一致性和可维护性。

一个接口,无限查询。如果没有复杂业务逻辑,这就是最终答案。

来源:码农看看

相关推荐