悠悠楠杉
在JPA中利用CriteriaAPI实现复杂查询与分页
首先,我们需要定义Article实体类:
java
@Entity
@Table(name = "articles")
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String keywords;
private String description;
private String content;
// 省略getter/setter
}
接下来,在Repository层使用JpaSpecificationExecutor接口来支持Specification,这是Spring Data JPA对Criteria API的封装,极大简化了复杂查询的实现。
创建仓库接口:
java
public interface ArticleRepository extends JpaRepository<Article, Long>, JpaSpecificationExecutor<Article> {
}
核心在于构建一个动态的Specification。我们可以定义一个静态方法,接收搜索关键词作为参数,返回一个Specification<Article>实例。这个实例将封装所有字段的“模糊匹配”逻辑,并通过Predicate的组合实现“或”关系查询。
java
public class ArticleSpecifications {
public static Specification<Article> containsInAnyField(String keyword) {
return (root, query, criteriaBuilder) -> {
if (keyword == null || keyword.trim().isEmpty()) {
return criteriaBuilder.conjunction(); // 返回恒真表达式
}
String likePattern = "%" + keyword.toLowerCase() + "%";
Predicate titlePred = criteriaBuilder.like(
criteriaBuilder.lower(root.get("title")), likePattern);
Predicate keywordsPred = criteriaBuilder.like(
criteriaBuilder.lower(root.get("keywords")), likePattern);
Predicate descriptionPred = criteriaBuilder.like(
criteriaBuilder.lower(root.get("description")), likePattern);
Predicate contentPred = criteriaBuilder.like(
criteriaBuilder.lower(root.get("content")), likePattern);
return criteriaBuilder.or(titlePred, keywordsPred, descriptionPred, contentPred);
};
}
}
上述代码中,我们通过root.get("field")获取实体属性路径,使用criteriaBuilder.lower()统一转为小写进行比较,确保搜索不区分大小写。like函数配合通配符实现模糊匹配。最终通过criteriaBuilder.or(...)将多个条件合并为一个整体谓词。
在服务层调用时,结合分页功能即可轻松实现完整的搜索逻辑:
java
@Service
public class ArticleService {
@Autowired
private ArticleRepository articleRepository;
public Page<Article> searchArticles(String keyword, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
Specification<Article> spec = ArticleSpecifications.containsInAnyField(keyword);
return articleRepository.findAll(spec, pageable);
}
}
这里使用了PageRequest来指定当前页码、每页数量及排序规则。findAll(Specification, Pageable)方法会自动处理分页并返回Page<Article>对象,包含内容列表和总页数等元信息,非常适合前后端分离架构中的接口响应。
值得注意的是,虽然Criteria API提供了强大的动态查询能力,但其语法相对繁琐,学习曲线较陡。不过一旦掌握,便能在不牺牲类型安全性的情况下灵活应对各种复杂业务场景。此外,由于整个查询过程由JPA Provider(如Hibernate)解析生成SQL,能够有效防止SQL注入,提升系统安全性。
在实际项目中,还可以进一步扩展此模式:例如加入时间范围筛选、状态过滤、多字段排序等功能,只需在Specification中继续添加相应的Predicate条件即可。这种模块化的设计使得查询逻辑清晰、易于测试和复用。
更重要的是,Criteria API与Spring生态无缝集成,配合@Query注解或自定义查询方法,可以灵活选择最适合当前场景的实现方式。对于高度动态的后台管理搜索界面而言,Specification无疑是比硬编码JPQL更优雅、更可持续的解决方案。

