摘要:如果你用过 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"这种模式可以轻松适配任何实体,让你的代码库保持一致性和可维护性。
一个接口,无限查询。如果没有复杂业务逻辑,这就是最终答案。
来源:码农看看
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!