在日常开发中,我们可能会遇到不固定的查询需求,如果仅用方法名或者@Query注解的方式定义查询语句,会导致方法爆炸,代码难以维护。因此JPA提供了Specification ,允许开发者以动态、类型安全的方式构建复杂查询。
一、Specification相关接口介绍
首先,需要继承 JpaRepository<T, ID> 和 JpaSpecificationExecutor<T> 接口,JpaSpecificationExecutor接口为Specification动态查询提供了支持:
public interface AdminRepository extends JpaRepository<Admin, Long>, JpaSpecificationExecutor<Admin> {
}
由JpaSpecificationExecutor接口中的源码可见,Specification可以作为查询参数传入JpaSpecificationExecutor接口的方法中:
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> spec);
List<T> findAll(@Nullable Specification<T> spec);
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
long count(@Nullable Specification<T> spec);
boolean exists(Specification<T> spec);
}
Specification接口内容如下:
public interface Specification<T> {
Predicate toPredicate(Root<T> root,
CriteriaQuery<?> query,
CriteriaBuilder cb);
}
- root:查询实体,
root.get("name")等价于SQL语句中的user.name。需要注意的是,get("name")中的名称必须是实体类中的字段名,不是数据库列名 - query:整个查询对象
- cb:条件构造器,包含
and,between,equal等方法,用于构建SQL查询语句,包含的其他方法可参考CriteriaBuilder的官方api文档
二、Specification常见的的使用方法
1、构造Specification:
public static Specification<User> build(
String name,
Integer minAge,
Integer maxAge) {
return (root, query, cb) -> {
//根据不同条件,确定是否添加查询语句
List<Predicate> predicates = new ArrayList<>();
if (name != null && !name.isEmpty()) {
predicates.add(
cb.like(root.get("name"), "%" + name + "%")
);
}
if (minAge != null) {
predicates.add(
cb.greaterThanOrEqualTo(root.get("age"), minAge)
);
}
if (maxAge != null) {
predicates.add(
cb.lessThanOrEqualTo(root.get("age"), maxAge)
);
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
2、调用查询方法:
Specification<User> spec = UserSpecification.build(
"Tom",
18,
30
);
Pageable pageable = PageRequest.of(0, 10);
Page<User> page = userRepository.findAll(spec, pageable);
生成的SQL语句类似于:
select * from user
where name like '%Tom%'
and age >= 18
and age <= 30
limit 0,10