可用于动态生成query,帮我们提供了一个高级的入口和结构,通过这个入口可以使用底层JPA的Criteria的所有方法,可以满足所有业务场景
public interface JpaSpecificationExecutor<T> { Optional<T> findOne(@Nullable Specification<T> var1); List<T> findAll(@Nullable Specification<T> var1); Page<T> findAll(@Nullable Specification<T> var1, Pageable var2); List<T> findAll(@Nullable Specification<T> var1, Sort var2); long count(@Nullable Specification<T> var1); }
这个接口是围绕着Specification接口来定义的:
public interface Specification<T> extends Serializable { long serialVersionUID = 1L; static <T> Specification<T> not(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : (root, query, builder) -> { return builder.not(spec.toPredicate(root, query, builder)); }; } static <T> Specification<T> where(@Nullable Specification<T> spec) { return spec == null ? (root, query, builder) -> { return null; } : spec; } default Specification<T> and(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::and); } default Specification<T> or(@Nullable Specification<T> other) { return SpecificationComposition.composed(this, other, CriteriaBuilder::or); } @Nullable Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3); }
需要子类是现实的主要方法是 toPredicate
Root<T> var1: 代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那root里面就是这张表里面的字段,是JPQL的实体字段,通过Path<Y>get(String var0)来获得操作的字段
CriteriaQuery<?> var2: 代表一个specific的顶层查询对象,它包含着查询的各个部分,如: select、form、where、group by、order by 等,它提供了查询var1的的方法,常用的有 where、select、having
CriteriaBuilder var3: 用来构建CriteriaQuery的构建器对象,其实就相当于条件或条件组合
示例:
@Test public void testJspSpecification() { SystemUser systemUser = new SystemUser(); systemUser.setUname("lis"); Specification<SystemUser> systemUserSpecification = (root, query, cb) -> { List<Predicate> predicates = new ArrayList<>(); // 模糊查询 if (systemUser.getUname() != null && systemUser.getUname().length() > 0){ predicates.add(cb.like(root.get("uname"),systemUser.getUname() + "%")); } // 日期比较 if (systemUser.getCreateTime() != null){ predicates.add(cb.greaterThanOrEqualTo(root.get("createTime").as(String.class),"2020-11-21 14:54:42")); } return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction(); }; }
Specification 的扩展
在实际使用中,动态条件通过不断创建Predicate对象设置参数完成,此时可以使用工厂模式帮我们做一些事情
public class SpecificationFactory { // 时区对象 private static final ZoneOffset ZONE_OFFSET = ZoneOffset.of("+8"); // 日期时间格式化对象 private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** * 模糊匹配头部, like %?1 * * @param fieldName 实体中的字段名称 * @param value 固定值 * @return 查询条件的封装对象 */ public static Specification likeStart(String fieldName, String value) { return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value); } /** * 模糊匹配尾部, like ?1% * * @param fieldName 实体中的字段名称 * @param value 固定值 * @return 查询条件的封装对象 */ public static Specification likeEnd(String fieldName, String value) { return (root, query, cb) -> cb.like(root.get(fieldName), value + "%"); } /** * 完全模糊匹配 , like %?1% * * @param fieldName 实体中的字段名称 * @param value 固定值 * @return 查询条件的封装对象 */ private static Specification like(String fieldName, String value) { return likeBuild(fieldName, "%" + value + "%"); } private static Specification likeBuild(String fieldName, String value) { return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value + "%"); } /** * 任意值相等比较 * * @param fieldName 实体中的字段名称 * @param value 比较值 * @return 查询条件的封装对象 */ public static <T> Specification eq(String fieldName, T value) { return (root, query, cb) -> cb.equal(root.get(fieldName), value); } /** * 比较日期区间 * * @param fieldName 实体中的字段名称 * @param min 最小日期值 * @param max 最大日期值 * @return 查询条件的封装对象 */ public static Specification betweenDate(String fieldName, Date min, Date max) { LocalDateTime lmin = LocalDateTime.ofInstant(min.toInstant(), ZONE_OFFSET); LocalDateTime lmax = LocalDateTime.ofInstant(max.toInstant(), ZONE_OFFSET); return (root, query, cb) -> cb.between(root.get(fieldName).as(String.class), DATE_TIME_FORMATTER.format(lmin), DATE_TIME_FORMATTER.format(lmax)); } /** * 比较任意值的区间 * * @param fieldName 实体中的字段名称 * @param min 最小值 * @param max 最大值 * @param <T> * @return 查询条件的封装对象 */ public static <T extends Comparable> Specification between(String fieldName, T min, T max) { return (root, query, cb) -> cb.between(root.get(fieldName), min, max); } /** * 数值大于比较 * * @param fieldName 实体中的字段名称 * @param value 比较值 * @param <T> * @return 查询条件的封装对象 */ public static <T extends Number> Specification gt(String fieldName, T value) { return (root, query, cb) -> cb.gt(root.get(fieldName).as(Number.class), value); } /** * 数值大于等于比较 * * @param fieldName 实体中的字段名称 * @param value 比较值 * @param <T> * @return 查询条件的封装对象 */ public static <T extends Comparable> Specification gte(String fieldName, T value) { return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(fieldName), value); } /** * 数值小于比较 * * @param fieldName 实体中的字段名称 * @param value 比较值 * @param <T> * @return 查询条件的封装对象 */ public static <T extends Number> Specification lt(String fieldName, T value) { return (root, query, cb) -> cb.lt(root.get(fieldName).as(Number.class), value); } /** * 数值小于等于比较 * * @param fieldName 实体中的字段名称 * @param value 比较值 * @param <T> * @return 查询条件的封装对象 */ public static <T extends Comparable> Specification lte(String fieldName, T value) { return (root, query, cb) -> cb.lessThanOrEqualTo(root.get(fieldName), value); } /** * 字段为null条件 * @param fieldName 实体中的字段名称 * @return 查询条件的封装对象 */ public static Specification isNull(String fieldName){ return (root, query, cb) -> cb.isNull(root.get(fieldName)); } /** * 字段不为null条件 * @param fieldName 实体中的字段名称 * @return 查询条件的封装对象 */ public static Specification isNotNull(String fieldName){ return (root, query, cb) -> cb.isNotNull(root.get(fieldName)); } /** * in 条件 * @param fieldName * @param values * @return */ public static Specification in(String fieldName,Object...values){ return (root, query, cb) -> root.get(fieldName).in(values); } }
用法:
@Test public void testJspSpecification() { // 传值 SystemUser systemUser = new SystemUser(); systemUser.setUname("lis"); systemUser.setEmail("@qq.com"); // 构造条件 Specification specification = SpecificationFactory.lt("createTime", new Date().getTime()); specification = specification.and(SpecificationFactory.likeEnd("uname", systemUser.getUname())); specification = specification.and(SpecificationFactory.isNull("email")); // 打印结果 List<SystemUser> systemUserList = this.userRepository.findAll(specification); for (SystemUser user : systemUserList) { System.out.println(user); } }