spring-boot-jpa 自定義查詢工具類


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));


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM