1.pom文件中添加如下配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
2.BaseQuery.java
package cn.springjpa.query;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName BaseQuery
* @Description
*/
public abstract class BaseQuery<T> {
// start from 0
protected int pageIndex = 0;
protected int pageSize = 10;
private static Map<Class, List<Field>> fieldCache = new HashMap<>();
/**
* 將查詢轉換成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
@SuppressWarnings({ "rawtypes", "unchecked" })
private Specification<T> toSpecWithLogicType(final String logicType) {
final BaseQuery outerThis = this;
//封裝條件查詢對象Specification
Specification<T> specification = new Specification<T>() {
@Override
// Root 用於獲取屬性字段,CriteriaQuery可以用於簡單條件查詢,CriteriaBuilder 用於構造復雜條件查詢
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Class clazz = outerThis.getClass();
//判斷緩存中是否已經存在,存在不需要再次生成,不存在需要重新生成
List<Field> fields = fieldCache.get(clazz);
if (fields == null) {
//獲取查詢類Query的所有字段,包括父類字段
fields = getAllFieldsWithRoot(clazz);
fieldCache.put(clazz, fields);
}
List<Predicate> predicates = new ArrayList<>(fields.size());
for (Field field : fields) {
//獲取字段上的@QueryWord注解
QueryCondition qw = field.getAnnotation(QueryCondition.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;
//如果值為"",且注解未標注empty,跳過
if (s.equals("") && !qw.empty())
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;
case between:
switch (qw.type()) {
case datetime:
List<Date> dateList = (List<Date>) value;
predicates.add(cb.between(path, dateList.get(0), dateList.get(1)));
break;
case number_long:
List<Long> longList = (List<Long>) value;
predicates.add(cb.between(path, longList.get(0), longList.get(1)));
break;
case number_integer:
List<Integer> integerList = (List<Integer>) value;
predicates.add(cb.between(path, integerList.get(0), integerList.get(1)));
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;
}
};
return specification;
}
//獲取類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()) {
for (Field field : superFields) {
if (!fieldList.contains(field)) {
fieldList.add(field);
}
}
}
return fieldList;
}
public int getPageIndex() {
return pageIndex;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
3.BetweenType.java
package cn.springjpa.query;
/**
* @ClassName BetweenType
* @Description between...and... 查詢語句標識,
*/
public enum BetweenType {
datetime,
number_long,
number_integer
}
4.MatchType.java
package cn.springjpa.query;
/**
* @ClassName MatchType
* @Description 列出所有的拼接條件
*/
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
between, // between value1 and value2 ,Type is Date
// 下面四個用於可比較類型(Comparable)的比較
greaterThan, // field > value
greaterThanOrEqualTo, // field >= value
lessThan, // field < value
lessThanOrEqualTo // field <= value
}
5.QueryCondition.java
package cn.springjpa.query;
import java.lang.annotation.*;
/**
* @ClassName QueryCondition
* @Description 自定義注解,用來標識字段
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface QueryCondition {
// 數據庫中字段名,默認為空字符串,則Query類中的字段要與數據庫中字段一致
String column() default "";
// equal, like, gt, lt...
MatchType func() default MatchType.equal;
// object是否可以為null
boolean nullable() default false;
// 字符串是否可為空
boolean empty() default false;
// between...and... 查詢語句標識, 0時間 1數字類型
BetweenType type() default BetweenType.datetime;
}
6.如何使用
(1) 將以上四個工具類放入自己的項目中
(2) 新建自己的查詢實體bean,可以參考如下的例子
/**
* @ClassName CertReqQuery
* @Description 查詢證書請求條件封裝
*/
public class CertReqQuery extends BaseQuery<CertReqEntity> {
@QueryCondition(func= MatchType.equal)
private Integer certReqStatus;
@QueryCondition(func= MatchType.equal)
private Long accountId;
public CertReqQuery() {
}
public CertReqQuery(Integer certReqStatus, Long accountId) {
this.certReqStatus = certReqStatus;
this.accountId = accountId;
}
public Integer getCertReqStatus() {
return certReqStatus;
}
public void setCertReqStatus(Integer certReqStatus) {
this.certReqStatus = certReqStatus;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
@Override
public Specification<CertReqEntity> toSpec() {
// 也可以寫自定義方法
return super.toSpecWithAnd();
}
}
(3) 接下來在service層調用即可,參考如下示例:
//設置查詢條件對象
CertReqQuery certReqQuery = new CertReqQuery(1, 123456L);
certReqQuery.setPageIndex(0;
certReqQuery.setPageSize(10);
return repo.findAll(certReqQuery.toSpec(), certReqQuery.toPageable(sort));
