天天看點

spring data jpa封裝specification實作簡單風格的動态查詢

github:https://github.com/peterowang/spring-data-jpa-demo

單一實體的動态查詢:

@Service
public class AdvancedUserInfoService{
    @Autowired
    UserInfoRepository userInfoRepository;
    /**
     * 簡單分頁排序查詢
     */
    public Page<UserInfo> pageList(int pageNo,int pageSize){
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable able = new PageRequest(pageNo, pageSize, sort);
        return userInfoRepository.findAll(able);
    }

    /**
     * 複雜動态多條件查詢
     * @param username
     * @param password
     * @param id
     * @return
     */
    public List<UserInfo> listDynamic(final String username,final String password,final Integer id){
        Specification<UserInfo> sf = new Specification<UserInfo>() {
            List<Predicate> list = new ArrayList<>();
            @Override
            public Predicate toPredicate(Root<UserInfo> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/*              Predicate p1 = cb.like(root.get("name").as(String.class), "%"+um.getName()+"%");
                Predicate p2 = cb.equal(root.get("uuid").as(Integer.class), um.getUuid());
                Predicate p3 = cb.gt(root.get("age").as(Integer.class), um.getAge());
                //把Predicate應用到CriteriaQuery中去,因為還可以給CriteriaQuery添加其他的功能,比如排序、分組啥的
                query.where(cb.and(p3,cb.or(p1,p2)));//where p3 and (p1 or p2)
                //添加排序的功能
                query.orderBy(cb.desc(root.get("uuid").as(Integer.class)));
                return query.getRestriction();*/
                List<Predicate> list = new ArrayList<>();
                if(!StringUtils.isEmpty(username)){
                    list.add(criteriaBuilder.like(root.get("username").as(String.class), "%" + username + "%"));
                }
                if(!StringUtils.isEmpty(password)){
                    list.add(criteriaBuilder.isNotNull(root.get("password").as(String.class)));
                }
                if(id!=null){
                    list.add(criteriaBuilder.greaterThanOrEqualTo(root.get("id").as(Integer.class),id));
                }
                Predicate[] pd = new Predicate[list.size()];
                criteriaQuery.where(list.toArray(pd));
                criteriaQuery.orderBy(criteriaBuilder.desc(root.get("id").as(Integer.class)));
                return criteriaQuery.getRestriction();
            }
        } ;
        return userInfoRepository.findAll(sf);
    }
    public Page<UserInfo> pageDynamic(final String username,final String password,final Integer id1,
                                      final Integer id2,final Integer pageNo,final Integer pageSize){
        return userInfoRepository.findAll(new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Predicate p1=null;Predicate p2=null; Predicate p3=null;
                if(StringUtils.isNotEmpty(username)){
                    p1 = criteriaBuilder.equal(root.get("username").as(String.class),username);
                }
                if(StringUtils.isNotEmpty(password)){
                    p2 = criteriaBuilder.equal(root.get("password").as(String.class), password);
                }
                if(id1!=null&&id2!=null){
                    p3 = criteriaBuilder.between(root.get("id").as(Integer.class), id1, id2);
                }
                criteriaQuery.where(criteriaBuilder.and(p1,criteriaBuilder.or(p2,p3)));
                return criteriaQuery.getRestriction();
            }
        },new PageRequest(pageNo,pageSize,new Sort(Sort.Direction.DESC,"id")));
    }
}      

Spring Data JPA已經幫助我們很大程度上簡化了我們的查詢操作,我們甚至隻要寫一個接口,然後單純的寫一些方法就可以完成各式各樣的查詢,但是對于我們程式設計人員而言,總希望所有的查詢變得更加的簡單友善,為了給程式人員進行再一次的封裝,Spring Data JPA提供了Specification的方式進行查詢,在前面的内容已經示範過這種查詢了,但是,我們在使用的過程中發現這種查詢異常的繁瑣和複雜,接下來的内容就是我們有效的對Specification進行封裝來快速實作一些簡單的查詢操作。當然如果涉及到更為複雜的操作,依然建議寫個方法來自己實作。

封裝自己的Specification的實作有很多種方法,我這裡隻給出了相對簡單的一種,而且并沒有考慮太複雜的查詢,個人感覺過于複雜的查詢還不如直接使用SQL或者HQL來處理友善,以下是幾個比較重要的類:

SpecificationOperator

表示操作符類,用來确定查詢條件和值。

package com.example.demo.SpecificationUtil;

/**
 * Created by BFD-593 on 2017/8/17.
 * 操作符類,這個類中存儲了鍵值對和操作符号,另外存儲了連接配接下一個條件的類型是and還是or
 * 建立時通過 id>=7,其中id就是key,>=就是oper操作符,7就是value
 * 特殊的自定義幾個操作符(:表示like %v%,l:表示v%,:l表示%v)
 */
public class SpecificationOperator {
    /**
     * 操作符的key,如查詢時的name,id之類
     */
    private String key;
    /**
     * 操作符的value,具體要查詢的值
     */
    private Object value;
    /**
     * 操作符,自己定義的一組操作符,用來友善查詢
     */
    private String oper;
    /**
     * 連接配接的方式:and或者or
     */
    private String join;

    public SpecificationOperator(String key, Object value, String oper, String join) {
        this.key = key;
        this.value = value;
        this.oper = oper;
        this.join = join;
    }
    public SpecificationOperator() {

    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public String getOper() {
        return oper;
    }

    public void setOper(String oper) {
        this.oper = oper;
    }

    public String getJoin() {
        return join;
    }

    public void setJoin(String join) {
        this.join = join;
    }
}
接下來建立         SimpleSpecification                來實作         Specification                接口,并且根據條件生成         Specification                對象,因為在最後查詢的時候需要這個對象      
package com.example.demo.SpecificationUtil;


import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;

/**
 * 建立SimpleSpecification來實作Specification接口,
 * 并且根據條件生成Specification對象,因為在最後查詢的時候需要這個對象
 * SimpleSpecification是核心類型,
 * 用來根據條件生成Specification對象,這個SimpleSpecification直接存儲了具體的查詢條件。
 * Created by BFD-593 on 2017/8/17.
 */
public class SimpleSpecification<T> implements Specification<T> {
    /**
     * 查詢的條件清單,是一組清單
     * */
    private List<SpecificationOperator> opers;

    public SimpleSpecification(List<SpecificationOperator> opers){
        this.opers=opers;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        int index = 0;
        Predicate resultPre = null;
        for(SpecificationOperator so :opers){
            if(index++==0){//第一次index=0    index++是先指派再加
                resultPre = generatePredicate(root, criteriaBuilder, so);
                continue;
            }
            Predicate pre = generatePredicate(root, criteriaBuilder, so);
            if(pre==null)continue;
            if("and".equalsIgnoreCase(so.getJoin())){
                resultPre = criteriaBuilder.and(resultPre, pre);
            }else if("or".equalsIgnoreCase(so.getJoin())){
                resultPre = criteriaBuilder.or(resultPre, pre);
            }
        }
        return resultPre;
    }

    private Predicate generatePredicate(Root<T> root,CriteriaBuilder criteriaBuilder,SpecificationOperator so){
        if(so!=null&&StringUtils.isNotEmpty(so.getOper())){
            if("=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.equal(root.get(so.getKey()), so.getValue());
            }else if(">=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.ge(root.get(so.getKey()).as(Number.class),(Number) so.getValue());
            }else if("<=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.le(root.get(so.getKey()).as(Number.class),(Number)so.getValue());
            }else if(">".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.gt(root.get(so.getKey()).as(Number.class), (Number) so.getValue());
            }else if("<".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.lt(root.get(so.getKey()).as(Number.class), (Number) so.getValue());
            }else if(":".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.like(root.get(so.getKey()).as(String.class), "%" + so.getValue() + "%");
            }else if(":l".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.like(root.get(so.getKey()).as(String.class), "%" + so.getValue());
            }else if("l:".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.like(root.get(so.getKey()).as(String.class), so.getValue() + "%");
            }else if("null".equalsIgnoreCase(so.getOper())){
                return criteriaBuilder.isNull(root.get(so.getKey()));
            }else if("!null".equalsIgnoreCase(so.getOper())){
                return criteriaBuilder.isNotNull(root.get(so.getKey()));
            }else if("!=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
                return criteriaBuilder.notEqual(root.get(so.getKey()), so.getValue());
            }
        }
        return null;
    }
}      

SimpleSpecification

是核心類型,用來根據條件生成Specification對象,這個

SimpleSpecification

直接存儲了具體的查詢條件。

最後我們建立一個

SimpleSpecificationBuilder

來具體建立

SimpleSpecification

,這裡為了友善調用簡單進行了一下設計。

package com.example.demo.SpecificationUtil;

import com.google.common.collect.Lists;
import org.springframework.data.jpa.domain.Specification;

import java.util.List;

/**
 * 建立一個SimpleSpecificationBuilder來具體建立SimpleSpecification,
 * 這裡為了友善調用簡單進行了一下設計。
 * Created by BFD-593 on 2017/8/17.
 */
public class SimpleSpecificationBuilder<T> {
    /**
     * 條件清單
     */
    private List<SpecificationOperator> opers;
    /**
     * 構造函數,初始化的條件是and
     */
    public SimpleSpecificationBuilder(String key,String oper,Object value,String join){
        SpecificationOperator so = new SpecificationOperator(key, value, oper, join);
        opers = Lists.newArrayList();
        opers.add(so);
    }

    /**
     * 構造,初始化無條件
     */
    public SimpleSpecificationBuilder(){
        opers = Lists.newArrayList();
    }

    /**
     * 往list中填加條件
     * @param key
     * @param oper
     * @param value
     * @param join
     * @return
     */
    public SimpleSpecificationBuilder add(String key,String oper,Object value,String join){
        SpecificationOperator so = new SpecificationOperator(key, value, oper, join);
        opers.add(so);
        return this;
    }

    /**
     * 填加一個and條件
     * @param key
     * @param oper
     * @param value
     * @return
     */
    public SimpleSpecificationBuilder and(String key,String oper,Object value){
        return this.add(key, oper, value, "and");
    }

    /**
     * 填加一個or條件
     * @param key
     * @param oper
     * @param value
     * @return
     */
    public SimpleSpecificationBuilder or(String key,String oper,Object value){
        return this.add(key, oper, value, "or");
    }
    /**
     * 觸發SimpleSpecification并傳回Specification
     */
    public Specification getSpecification(){
        Specification<T> sp = new SimpleSpecification<T>(opers);
        return sp;
    }
}
測試:


      
/**
 * 在多條件動态查詢時需要繼承JpaSpecificationExecutor接口
 * JpaSpecificationExecutor可以通過findAll方法傳入SimpleSpecification來進行查詢
 * Created by BFD-593 on 2017/8/16.
 */
public interface RoleRepository extends BaseRepository<Role,Integer>,JpaSpecificationExecutor<Role> {

}      
/**
 * 測試封裝的specification
 * 實作簡單風格的動态查詢
 * id < id and roleName like %roleName% or id>id and roleName like roleName%的動态查詢
 * 某個參數為空時,就不使用該參數所在的條件。
 * @param roleName
 * @param id
 * @return
 */
public List<Role> spe(String roleName,Integer id) {
    return roleRepository.findAll(new SimpleSpecificationBuilder<Role>().
            and("id", "<", id).
            and("roleName",":",roleName).
            or("id",">",id).
            and("roleName","l:",roleName).
            getSpecification());
}      

轉載于:https://www.cnblogs.com/wangjing666/p/7383121.html