Spring Data JPA 的 JpaSpecificationExecutor 配合 Specification 接口,提供了強大的類型安全動態查詢能力。本文深入剖析 Criteria API 核心組件,並通過兩個完整業務示例展示多條件組合、關聯子查詢、日期範圍等實戰技巧。
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
}
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
Predicate、Expression、Order 等查詢元素的工廠root.get("field")and/or/notpublic interface DevHREmpConstrastDao extends JpaRepository<DevHREmpConstrast, Long>,
JpaSpecificationExecutor<DevHREmpConstrast> { }
private Specification<DevHREmpConstrast> buildSpec(
final String corg, final String name, final String type,
final String date, final String checker) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 1. 機構名稱模糊匹配 → 關聯子查詢獲取組織ID列表
if (hasText(corg)) {
List<String> orgIds = organizationDao.findByName("%" + corg + "%");
if (validSize(orgIds))
predicates.add(root.get("confirmOrgNo").in(orgIds));
}
// 2. 員工姓名模糊匹配 → 關聯子查詢獲取用戶ID列表
if (hasText(name)) {
List<String> userIds = userDao.findByName(name);
if (validSize(userIds))
predicates.add(root.get("hrUserName").in(userIds));
}
// 3. 變更類型精確匹配
if (hasText(type))
predicates.add(cb.equal(root.get("hrUpdateType"), type));
// 4. 日期範圍查詢(當天 00:00:00 ~ 23:59:59)
if (hasText(date)) {
try {
Date start = new SimpleDateFormat("yyyy-MM-dd").parse(date);
Calendar cal = Calendar.getInstance();
cal.setTime(start);
cal.add(Calendar.DAY_OF_MONTH, 1);
Date end = cal.getTime();
predicates.add(cb.between(root.get("insDate"), start, end));
} catch (ParseException e) {
// 忽略格式錯誤
}
}
// 5. 審核人模糊匹配
if (hasText(checker)) {
List<String> userIds = userDao.findByName(checker);
if (validSize(userIds))
predicates.add(root.get("confirmUserId").in(userIds));
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
};
}
// 輔助方法
private boolean hasText(String s) { return s != null && !s.isEmpty(); }
private boolean validSize(List<?> list) { return list != null && list.size() > 0 && list.size() < 1000; }
@Autowired DevHREmpConstrastDao dao;
public Page<DevHREmpConstrast> search(String corg, String name, String type,
String date, String checker, Pageable pageable) {
return dao.findAll(buildSpec(corg, name, type, date, checker), pageable);
}
root.get("field").in(ids),避免複雜 JoinCalendar 計算次日 0 點作為上界,確保包含全天root.<String>get("field") 編譯期檢查字段類型cb.and()、cb.or()、cb.not() 任意嵌套| 維度 | HQL 字符串拼接 | Specification + Criteria |
|---|---|---|
| 類型安全 | 運行期報錯 | 編譯期檢查 |
| 防注入 | 需手動參數綁定 | 自動參數化 |
| 重構友好 | 字段重命名易出錯 | IDE 重構自動更新 |
| 可讀性 | 碎片化字符串 | 流式 API 結構清晰 |
掌握 Specification 模式,可構建出既靈活又健壯的動態查詢層,是 Spring Data JPA 進階開發的必備技能。