在開發中,用到動態查詢的地方,全部的查詢條件包括分頁參數,都會被封裝成一個查詢類XxxQuery
java
好比說上一篇中的Item
數據庫
那麼ItemQuery
就像這樣less
@Data
public class ItemQuery {
private Integer itemId;//id精確查詢 =
private String itemName;//name模糊查詢 like
//價格查詢
private Integer itemPrice;// 價格小於'條件' <
}複製代碼
那如今問題來了,如何去標識這些字段該用怎樣的查詢條件鏈接呢,還要考慮到每一個查詢類均可以通用.ide
能夠用字段註解,來標識字段的查詢鏈接條件ui
//用枚舉類表示查詢鏈接條件
public enum MatchType {
equal, // filed = value
//下面四個用於Number類型的比較
gt, // filed > value
ge, // field >= value
lt, // field < value
le, // field <= value
notEqual, // field != value
like, // field like value
notLike, // field not like value
// 下面四個用於可比較類型(Comparable)的比較
greaterThan, // field > value
greaterThanOrEqualTo, // field >= value
lessThan, // field < value
lessThanOrEqualTo, // 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是否能夠爲null
boolean nullable() default false;
// 字符串是否可爲空
boolean emptiable() default false;
}複製代碼
好了,如今咱們能夠改造一下ItemQuery
了this
@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
,咱們把分頁的條件字段放在基類裏.spa
/** * 全部查詢類的基類 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseQuery<T> {
// start from 0
protected 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/and
private 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 {
// nullable
Object value = field.get(outerThis);
//若是值爲null,註解未標註nullable,跳過
if (value == null && !qw.nullable())
continue;
// can be empty
if (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,包括其父類的Field
private 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()
方法動態構建出了查詢條件.code
那如今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;
@Override
public 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);
}複製代碼