如何在Criteria API中更简洁处理可为空的可选搜索参数?
简化JPA Criteria查询中空参数处理的方案
先假设你的实体类示例如下(常见场景模拟):
@Entity public class User { @Id private Long id; private String username; private Integer age; private LocalDate joinDate; // getter、setter 省略 }
以下是几种替代显式空值检查的简洁实现方案:
方案1:封装工具类复用判空+断言逻辑
把"判空+生成Predicate"的通用逻辑抽成工具方法,避免重复代码:
public class CriteriaUtils { // 字符串模糊查询处理 public static Optional<Predicate> likeIfPresent(CriteriaBuilder cb, Expression<String> expr, String value) { return Optional.ofNullable(value) .filter(v -> !v.isBlank()) .map(v -> cb.like(cb.lower(expr), "%" + v.toLowerCase() + "%")); } // 数值精确匹配处理 public static <T extends Number> Optional<Predicate> equalIfPresent(CriteriaBuilder cb, Expression<T> expr, T value) { return Optional.ofNullable(value) .map(v -> cb.equal(expr, v)); } // 日期范围起始值处理 public static Optional<Predicate> greaterThanOrEqualToIfPresent(CriteriaBuilder cb, Expression<LocalDate> expr, LocalDate value) { return Optional.ofNullable(value) .map(v -> cb.greaterThanOrEqualTo(expr, v)); } }
查询代码中通过Stream收集非空断言:
public List<User> searchUsers(String username, Integer age, LocalDate startJoinDate) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> cq = cb.createQuery(User.class); Root<User> root = cq.from(User.class); List<Predicate> predicates = Stream.of( CriteriaUtils.likeIfPresent(cb, root.get("username"), username), CriteriaUtils.equalIfPresent(cb, root.get("age"), age), CriteriaUtils.greaterThanOrEqualToIfPresent(cb, root.get("joinDate"), startJoinDate) ) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); cq.where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(cq).getResultList(); }
方案2:用Java 8 Optional链式调用简化单参数处理
无需额外工具类,直接在查询代码中用Optional替代显式if判空:
public List<User> searchUsers(String username, Integer age, LocalDate startJoinDate) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> cq = cb.createQuery(User.class); Root<User> root = cq.from(User.class); List<Predicate> predicates = new ArrayList<>(); // 处理用户名模糊查询 Optional.ofNullable(username) .filter(s -> !s.isBlank()) .map(s -> cb.like(cb.lower(root.get("username")), "%" + s.toLowerCase() + "%")) .ifPresent(predicates::add); // 处理年龄精确匹配 Optional.ofNullable(age) .map(a -> cb.equal(root.get("age"), a)) .ifPresent(predicates::add); // 处理注册起始日期 Optional.ofNullable(startJoinDate) .map(date -> cb.greaterThanOrEqualTo(root.get("joinDate"), date)) .ifPresent(predicates::add); cq.where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(cq).getResultList(); }
方案3:借助第三方库(Querydsl)简化动态查询
如果项目允许引入第三方库,Querydsl可自动处理空参数,大幅简化代码:
先引入Maven依赖:
<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency>
编写查询代码:
public List<User> searchUsers(String username, Integer age, LocalDate startJoinDate) { JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); QUser qUser = QUser.user; return queryFactory.selectFrom(qUser) .where( Optional.ofNullable(username) .filter(s -> !s.isBlank()) .map(s -> qUser.username.toLowerCase().contains(s.toLowerCase())) .orElse(null), Optional.ofNullable(age).map(qUser.age::eq).orElse(null), Optional.ofNullable(startJoinDate).map(qUser.joinDate::goe).orElse(null) ) .fetch(); }
方案优势对比
- 移除了重复的空值判断代码,整体更简洁
- 逻辑复用性强(工具类/第三方库方式)
- 可读性提升,每个参数的查询逻辑清晰直观
内容的提问来源于stack exchange,提问作者Sergey Zolotarev
相关产品推荐
相关产品推荐

