當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
Spring-Data-JPA 动态查询黑科技
生活随笔
收集整理的這篇文章主要介紹了
Spring-Data-JPA 动态查询黑科技
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在開發中,用到動態查詢的地方,所有的查詢條件包括分頁參數,都會被封裝成一個查詢類XxxQuery
比如說上一篇中的Item
那么ItemQuery就像這樣
@Data public class ItemQuery {private Integer itemId;//id精確查詢 =private String itemName;//name模糊查詢 like//價格查詢private Integer itemPrice;// 價格小于'條件' < }那現在問題來了,如何去標識這些字段該用怎樣的查詢條件連接呢,還要考慮到每個查詢類都可以通用.
可以用字段注解,來標識字段的查詢連接條件
//用枚舉類表示查詢連接條件 public enum MatchType {equal, // filed = value//下面四個用于Number類型的比較gt, // filed > valuege, // field >= valuelt, // field < valuele, // field <= valuenotEqual, // field != valuelike, // field like valuenotLike, // field not like value// 下面四個用于可比較類型(Comparable)的比較greaterThan, // field > valuegreaterThanOrEqualTo, // field >= valuelessThan, // field < valuelessThanOrEqualTo, // field <= value; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface QueryWord {// 數據庫中字段名,默認為空字符串,則Query類中的字段要與數據庫中字段一致String column() default "";// equal, like, gt, lt...MatchType func() default MatchType.equal;// object是否可以為nullboolean nullable() default false;// 字符串是否可為空boolean emptiable() default false; }好了,現在我們可以改造一下ItemQuery了
@Data public class ItemQuery {@QueryWord(column = "item_id", func = MatchType.equal)private Integer itemId;@QueryWord(func = MatchType.like)private String itemName;@QueryWord(func = MatchType.le)private Integer itemPrice; }現在,我們還需要去構造出查詢時的動態條件,那就創建一個所有查詢類的基類BaseQuery,我們把分頁的條件字段放在基類里.
/*** 所有查詢類的基類*/ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public abstract class BaseQuery<T> {// start from 0protected int pageIndex = 0;protected int pageSize = 10;/*** 將查詢轉換成Specification* @return*/public abstract Specification<T> toSpec();//JPA分頁查詢類public Pageable toPageable() {return new PageRequest(pageIndex, pageSize);}//JPA分頁查詢類,帶排序條件public Pageable toPageable(Sort sort) {return new PageRequest(pageIndex, pageSize, sort);}//動態查詢and連接protected Specification<T> toSpecWithAnd() {return this.toSpecWithLogicType("and");}//動態查詢or連接protected Specification<T> toSpecWithOr() {return this.toSpecWithLogicType("or");}//logicType or/andprivate Specification<T> toSpecWithLogicType(String logicType) {BaseQuery outerThis = this;return (root, criteriaQuery, cb) -> {Class clazz = outerThis.getClass();//獲取查詢類Query的所有字段,包括父類字段List<Field> fields = getAllFieldsWithRoot(clazz);List<Predicate> predicates = new ArrayList<>(fields.size());for (Field field : fields) {//獲取字段上的@QueryWord注解QueryWord qw = field.getAnnotation(QueryWord.class);if (qw == null)continue;// 獲取字段名String column = qw.column();//如果主注解上colume為默認值"",則以field為準if (column.equals(""))column = field.getName();field.setAccessible(true);try {// nullableObject value = field.get(outerThis);//如果值為null,注解未標注nullable,跳過if (value == null && !qw.nullable())continue;// can be emptyif (value != null && String.class.isAssignableFrom(value.getClass())) {String s = (String) value;//如果值為"",且注解未標注emptyable,跳過if (s.equals("") && !qw.emptiable())continue;}//通過注解上func屬性,構建路徑表達式Path path = root.get(column);switch (qw.func()) {case equal:predicates.add(cb.equal(path, value));break;case like:predicates.add(cb.like(path, "%" + value + "%"));break;case gt:predicates.add(cb.gt(path, (Number) value));break;case lt:predicates.add(cb.lt(path, (Number) value));break;case ge:predicates.add(cb.ge(path, (Number) value));break;case le:predicates.add(cb.le(path, (Number) value));break;case notEqual:predicates.add(cb.notEqual(path, value));break;case notLike:predicates.add(cb.notLike(path, "%" + value + "%"));break;case greaterThan:predicates.add(cb.greaterThan(path, (Comparable) value));break;case greaterThanOrEqualTo:predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));break;case lessThan:predicates.add(cb.lessThan(path, (Comparable) value));break;case lessThanOrEqualTo:predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));break;}} catch (Exception e) {continue;}}Predicate p = null;if (logicType == null || logicType.equals("") || logicType.equals("and")) {p = cb.and(predicates.toArray(new Predicate[predicates.size()]));//and連接} else if (logicType.equals("or")) {p = cb.or(predicates.toArray(new Predicate[predicates.size()]));//or連接}return p;};}//獲取類clazz的所有Field,包括其父類的Fieldprivate List<Field> getAllFieldsWithRoot(Class<?> clazz) {List<Field> fieldList = new ArrayList<>();Field[] dFields = clazz.getDeclaredFields();//獲取本類所有字段if (null != dFields && dFields.length > 0)fieldList.addAll(Arrays.asList(dFields));// 若父類是Object,則直接返回當前Field列表Class<?> superClass = clazz.getSuperclass();if (superClass == Object.class) return Arrays.asList(dFields);// 遞歸查詢父類的field列表List<Field> superFields = getAllFieldsWithRoot(superClass);if (null != superFields && !superFields.isEmpty()) {superFields.stream().filter(field -> !fieldList.contains(field)).//不重復字段forEach(field -> fieldList.add(field));}return fieldList;} }在BaseQuery里,就通過toSpecWithAnd() toSpecWithOr()方法動態構建出了查詢條件.
那現在ItemQuery就要繼承BaseQuery,并實現toSpec()抽象方法
@Data public class ItemQuery extends BaseQuery<Item> {@QueryWord(column = "item_id", func = MatchType.equal)private Integer itemId;@QueryWord(func = MatchType.like)private String itemName;@QueryWord(func = MatchType.le)private Integer itemPrice;@Overridepublic Specification<Item> toSpec() {return super.toSpecWithAnd();//所有條件用and連接} }當然肯定還有其他不能在BaseQuery中構建的查詢條件,那就在子類的toSpec()實現中添加,
比如下面的例子,ItemQuery條件改成這樣
@QueryWord(column = "item_id", func = MatchType.equal) private Integer itemId; @QueryWord(func = MatchType.like) private String itemName; //價格范圍查詢 private Integer itemPriceMin; private Integer itemPriceMax;那其他條件就可以在toSpec()添加,這樣就可以很靈活的構建查詢條件了
@Override public Specification<Item> toSpec() {Specification<Item> spec = super.toSpecWithAnd();return ((root, criteriaQuery, criteriaBuilder) -> {List<Predicate> predicatesList = new ArrayList<>();predicatesList.add(spec.toPredicate(root, criteriaQuery, criteriaBuilder));if (itemPriceMin != null) {predicatesList.add(criteriaBuilder.and(criteriaBuilder.ge(root.get(Item_.itemPrice), itemPriceMin)));}if (itemPriceMax != null) {predicatesList.add(criteriaBuilder.and(criteriaBuilder.le(root.get(Item_.itemPrice), itemPriceMax)));}return criteriaBuilder.and(predicatesList.toArray(new Predicate[predicatesList.size()]));}); }調用:
@Test public void test1() throws Exception {ItemQuery itemQuery = new ItemQuery();itemQuery.setItemName("車");itemQuery.setItemPriceMax(50);itemQuery.setItemPriceMax(200);Pageable pageable = itemQuery.toPageable(new Sort(Sort.Direction.ASC, "itemId"));Page<Item> all = itemRepository.findAll(itemQuery.toSpec(), pageable); }現在這個BaseQuery和QuertWord就可以在各個動態查詢處使用了,只需在查詢字段上標注@QueryWord注解,
然后實現BaseQuery中的抽象方法toSpec(),通過JpaSpecificationExecutor接口中的這幾個方法,就可以實現動態查詢了,是不是很方便.
public interface JpaSpecificationExecutor<T> {T findOne(Specification<T> var1);List<T> findAll(Specification<T> var1);Page<T> findAll(Specification<T> var1, Pageable var2);List<T> findAll(Specification<T> var1, Sort var2);long count(Specification<T> var1); }總結
以上是生活随笔為你收集整理的Spring-Data-JPA 动态查询黑科技的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 循环中套查询优化
- 下一篇: : Content type 'text