Spring Data JPA Specifications 实现动态查询

Alex Chen | 2026-09-02T01:06:56 | Java, Spring Boot

通过 Specification 接口和 Criteria API 构建类型安全的动态查询条件,告别手写 SQL 拼接,适用于复杂筛选和后台管理搜索场景。

# Spring Data JPA Specifications 实现动态查询 ## 问题背景 在后台管理系统中,搜索条件往往是动态的:用户可能只填了名称,也可能同时填了名称、状态和时间范围。传统做法是拼接 SQL 或写大量 if-else,不仅丑陋还容易出 bug。 Spring Data JPA 提供的 `Specification` 接口完美解决了这个问题。 ## 基础用法 首先让 Repository 继承 `JpaSpecificationExecutor`: ```java public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { } ``` 然后创建 Specification: ```java public class UserSpecs { public static Specification hasName(String name) { return (root, query, cb) -> { if (name == null || name.isBlank()) { return cb.conjunction(); } return cb.like(root.get("name"), "%" + name + "%"); }; } public static Specification hasStatus(Integer status) { return (root, query, cb) -> { if (status == null) { return cb.conjunction(); } return cb.equal(root.get("status"), status); }; } public static Specification createdBetween(LocalDateTime start, LocalDateTime end) { return (root, query, cb) -> { List predicates = new ArrayList(); if (start != null) { predicates.add(cb.greaterThanOrEqualTo( root.get("createdAt"), start)); } if (end != null) { predicates.add(cb.lessThanOrEqualTo( root.get("createdAt"), end)); } return cb.and(predicates.toArray(new Predicate[0])); }; } } ``` ## 组合查询 在 Service 中将多个 Specification 通过 `and()` / `or()` 组合: ```java @Service public class UserService { @Autowired private UserRepository userRepository; public Page search(UserSearchDTO dto, Pageable pageable) { Specification spec = Specification .where(UserSpecs.hasName(dto.getName())) .and(UserSpecs.hasStatus(dto.getStatus())) .and(UserSpecs.createdBetween(dto.getStartTime(), dto.getEndTime())); return userRepository.findAll(spec, pageable); } } ``` ## 进阶:通用查询构建器 当条件变得更复杂时,可以封装一个通用构建器: ```java public class SpecBuilder { private Specification spec; public SpecBuilder() { this.spec = Specification.where(null); } public SpecBuilder eq(String field, Object value) { if (value != null) { spec = spec.and((root, q, cb) -> cb.equal(root.get(field), value)); } return this; } public SpecBuilder like(String field, String value) { if (value != null && !value.isBlank()) { spec = spec.and((root, q, cb) -> cb.like(root.get(field), "%" + value + "%")); } return this; } public SpecBuilder between(String field, Comparable lo, Comparable hi) { if (lo != null) { spec = spec.and((root, q, cb) -> cb.greaterThanOrEqualTo(root.get(field), (Comparable) lo)); } if (hi != null) { spec = spec.and((root, q, cb) -> cb.lessThanOrEqualTo(root.get(field), (Comparable) hi)); } return this; } public Specification build() { return spec; } } ``` 使用: ```java Specification spec = new SpecBuilder() .eq("status", statusParam) .like("customerName", nameParam) .between("amount", minAmount, maxAmount) .build(); ``` ## Join 查询 处理关联表查询同样简洁: ```java public static Specification hasCategoryName(String categoryName) { return (root, query, cb) -> { if (categoryName == null) return cb.conjunction(); Join categoryJoin = root.join("category", JoinType.LEFT); return cb.equal(categoryJoin.get("name"), categoryName); }; } ``` ## 性能提示 1. 对于 count 查询(分页时自动触发),避免不必要的 fetch join 2. 使用 `query.distinct(true)` 防止 join 导致的重复行 3. 索引覆盖常用查询字段 4. 复杂查询考虑使用 `@QueryHints` 添加缓存提示 ## 对比其他方案 | 方案 | 类型安全 | 动态条件 | 学习成本 | 可维护性 | |------|---------|---------|---------|---------| | Specification | 是 | 强 | 中 | 高 | | QueryDSL | 是 | 强 | 高 | 高 | | @Query JPQL | 否 | 弱 | 低 | 中 | | Native SQL | 否 | 弱 | 低 | 低 | ## 总结 Specification 是 Spring Data JPA 中处理动态查询的最佳实践。它提供了类型安全、可组合、可测试的查询构建方式,特别适合后台管理系统中复杂的筛选场景。结合通用构建器模式,可以大幅减少重复代码。

← Back to Blog