可用於動態生成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); } }
