SpringDataJpa的Specification查詢(轉)


摘要: Spring Data JPA支持JPA2.0的Criteria查詢,相應的接口是JpaSpecificationExecutor。 Criteria 查詢:是一種類型安全和更面向對象的查詢

Spring Data JPA支持JPA2.0的Criteria查詢,相應的接口是JpaSpecificationExecutor。Criteria 查詢:是一種類型安全和更面向對象的查詢 。

這個接口基本是圍繞着Specification接口來定義的, Specification接口中只定義了如下一個方法:

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 

要理解這個方法,以及正確的使用它,就需要對JPA2.0的Criteria查詢有一個足夠的熟悉和理解,因為這個方法的參數和返回值都是JPA標准里面定義的對象。 

Criteria查詢基本概念

Criteria 查詢是以元模型的概念為基礎的,元模型是為具體持久化單元的受管實體定義的,這些實體可以是實體類,嵌入類或者映射的父類。

CriteriaQuery接口:代表一個specific的頂層查詢對象,它包含着查詢的各個部分,比如:select 、from、where、group by、order by等注意:CriteriaQuery對象只對實體類型或嵌入式類型的Criteria查詢起作用

Root接口:代表Criteria查詢的根對象,Criteria查詢的查詢根定義了實體類型,能為將來導航獲得想要的結果,它與SQL查詢中的FROM子句類似

1:Root實例是類型化的,且定義了查詢的FROM子句中能夠出現的類型。

2:查詢根實例能通過傳入一個實體類型給 AbstractQuery.from方法獲得。

3:Criteria查詢,可以有多個查詢根。 

4:AbstractQuery是CriteriaQuery 接口的父類,它提供得到查詢根的方法。CriteriaBuilder接口:用來構建CritiaQuery的構建器對象Predicate:一個簡單或復雜的謂詞類型,其實就相當於條件或者是條件組合。 

Criteria查詢基本對象的構建

1:通過EntityManager的getCriteriaBuilder或EntityManagerFactory的getCriteriaBuilder方法可以得到CriteriaBuilder對象2:通過調用CriteriaBuilder的createQuery或createTupleQuery方法可以獲得CriteriaQuery的實例

3:通過調用CriteriaQuery的from方法可以獲得Root實例過濾條件

    A:過濾條件會被應用到SQL語句的FROM子句中。在criteria 查詢中,查詢條件通過Predicate或Expression實例應用到CriteriaQuery對象上。

    B:這些條件使用 CriteriaQuery .where 方法應用到CriteriaQuery 對象上

    C:CriteriaBuilder也作為Predicate實例的工廠,通過調用CriteriaBuilder 的條件方( equal,notEqual, gt, ge,lt, le,between,like等)創建Predicate對象。

    D:復合的Predicate 語句可以使用CriteriaBuilder的and, or andnot 方法構建。 

        構建簡單的Predicate示例:

            Predicate p1=cb.like(root.get(“name”).as(String.class), “%”+uqm.getName()+“%”);

            Predicate p2=cb.equal(root.get("uuid").as(Integer.class), uqm.getUuid());

            Predicate p3=cb.gt(root.get("age").as(Integer.class), uqm.getAge());

        構建組合的Predicate示例:

           Predicate p = cb.and(p3,cb.or(p1,p2)); 

        當然也可以形如前面動態拼接查詢語句的方式,比如:

Specification<UserModel> spec = new Specification<UserModel>() {       public Predicate toPredicate(Root<UserModel> root,               CriteriaQuery<?> query, CriteriaBuilder cb) {           List<Predicate> list = new ArrayList<Predicate>();                          if(um.getName()!=null && um.getName().trim().length()>0){               list.add(cb.like(root.get("name").as(String.class), "%"+um.getName()+"%"));           }           if(um.getUuid()>0){               list.add(cb.equal(root.get("uuid").as(Integer.class), um.getUuid()));           }           Predicate[] p = new Predicate[list.size()];           return cb.and(list.toArray(p));       }   };

        也可以使用CriteriaQuery來得到最后的Predicate,示例如下:

Specification<UserModel> spec = new Specification<UserModel>() {       public Predicate toPredicate(Root<UserModel> root,               CriteriaQuery<?> query, CriteriaBuilder cb) {           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)));           //添加排序的功能           query.orderBy(cb.desc(root.get("uuid").as(Integer.class)));                      return query.getRestriction();       }   };

綜上所述,我們可以用Specification的toPredicate方法構建更多更復雜的查詢方法。甚至如果你想多表鏈接進行查詢,首先在對象上用@onetoone、@onetomany、@manytoone等注解,然后利用root.jion()方法也能做到,還 可以設定鏈表方式等。具體可以查看相關jpa文檔。

下面我們用兩個示例代碼來更深入的了解:

    1.復雜條件多表查詢

//需要查詢的對象 public class Qfjbxxdz {     @Id     @GeneratedValue(generator = "system-uuid")     @GenericGenerator(name = "system-uuid", strategy = "uuid.hex")     private String id;     @OneToOne     @JoinColumn(name = "qfjbxx")     private Qfjbxx qfjbxx; //關聯表     private String fzcc;     private String fzccName;     @ManyToOne     @JoinColumn(name = "criminalInfo")     private CriminalInfo criminalInfo;//關聯表     @Column(length=800)     private String bz;     //get/set...... }
//創建構造Specification的方法 //這里我傳入兩個條件參數因為與前段框架有關,你們寫的時候具體自己業務自行決斷 private Specification<Qfjbxxdz> getWhereClause(final JSONArray condetion,final JSONArray search) {         return new Specification<Qfjbxxdz>() {             @Override             public Predicate toPredicate(Root<Qfjbxxdz> root, CriteriaQuery<?> query, CriteriaBuilder cb) {                 List<Predicate> predicate = new ArrayList<>();                 Iterator<JSONObject> iterator = condetion.iterator();                 Predicate preP = null;                 while(iterator.hasNext()){                     JSONObject jsonObject = iterator.next();                     //注意:這里用的root.join 因為我們要用qfjbxx對象里的字段作為條件就必須這樣做join方法有很多重載,使用的時候可以多根據自己業務決斷                     Predicate p1 = cb.equal(root.join("qfjbxx").get("id").as(String.class),jsonObject.get("fzId").toString());                     Predicate p2 = cb.equal(root.get("fzcc").as(String.class),jsonObject.get("ccId").toString());                     if(preP!=null){                         preP = cb.or(preP,cb.and(p1,p2));                     }else{                         preP = cb.and(p1,p2);                     }                 }                 JSONObject jsonSearch=(JSONObject) search.get(0);                 Predicate p3=null;                 if(null!=jsonSearch.get("xm")&&jsonSearch.get("xm").toString().length()>0){                    p3=cb.like(root.join("criminalInfo").get("xm").as(String.class),"%"+jsonSearch.get("xm").toString()+"%");                 }                 Predicate p4=null;                 if(null!=jsonSearch.get("fzmc")&&jsonSearch.get("fzmc").toString().length()>0){                     p4=cb.like(root.join("qfjbxx").get("fzmc").as(String.class),"%"+jsonSearch.get("fzmc").toString()+"%");                 }                 Predicate preA;                 if(null!=p3&&null!=p4){                     Predicate  preS =cb.and(p3,p4);                     preA =cb.and(preP,preS);                 }else if(null==p3&&null!=p4){                     preA=cb.and(preP,p4);                 }else if(null!=p3&&null==p4){                     preA=cb.and(preP,p3);                 }else{                     preA=preP;                 }                 predicate.add(preA);                 Predicate[] pre = new Predicate[predicate.size()];                 query.where(predicate.toArray(pre));                 return query.getRestriction();             }         };     }
public void findByPage(JSONArray condetion,JSONArray search) {     Pageable pageable = ....//構建分頁對象      Specification<Qfjbxxdz> spec = getWhereClause(condetion,search)     //利用JpaSpecificationExecutor接口的分頁查詢方法:     //Page<T> findAll(Specification<T> spec, Pageable pageable);     //這就是上一個文章里我為什么要繼承JpaSpecificationExecutor接口的原因     Page<Qfjbxxdz> page = qfjbxxdzRepository.findAll(spec, pageable ); }

    2.EntityManager的使用

@PersistenceContext private EntityManager em;//注入entitymanager
//A.利用entitymanager構建Criteria查詢 //其實和sql一樣,就是換成了面向對象的方式。這種方式的好處就是可以防止sql拼寫錯誤。當然這種方式也不是特別直觀 public List<Qfsqmx> countQfsq(List<Qfsq> list) {     CriteriaBuilder cb = em.getCriteriaBuilder();     CriteriaQuery<Qfsqmx> q = cb.createQuery(Qfsqmx.class);     Root<Qfsqmx> root = q.from(Qfsqmx.class);     List<Predicate> predicate = new ArrayList<>();     //in條件拼接     In<String> in = cb.in(root.get("qfsqid").as(String.class));     for (int i = 0; i < list.size(); i++) {         in.value(list.get(i).getId());     }    //設定select字段     q.multiselect(         root.get("qfmc"),         root.get("qfcc"),         root.get("jldw"),         cb.sum(root.get("sl").as(Integer.class))     );     predicate.add(in);     Predicate[] pre = new Predicate[predicate.size()];     //設定where條件     q.where(predicate.toArray(pre));     //設定groupby條件     q.groupBy(         root.get("qflxid").as(String.class),         root.get("qfccid").as(String.class),         root.get("qfmc").as(String.class),         root.get("qfcc").as(String.class),         root.get("jldw").as(String.class)     );     //設定orderby條件     q.orderBy(cb.asc(root.get("qfmc").as(String.class)));     List<Qfsqmx> rs = em.createQuery(q).getResultList();     return rs; }
//B.利用em進行本地sql分頁查詢 public JsonReader selectSrzc(QueryParams queryParams,String qsrq, String jzrq, String zmlx, String zlx, String zhlx, String ssjq, String zfxm) {     StringBuffer hql = new StringBuffer();      hql.append("SELECT to_char(srzc.sj,'yyyy-mm-dd'),ZFZH.ZHBH,ZF.xm,zf.gyjq,decode(srzc.zhlx,'ykt','***','grzh','***'),srzc.zmlx,srzc.zlx,srzc.jsr,srzc.srzc,ye.ye,srzc.sjly FROM ");     hql.append("(SELECT * FROM (");     hql.append("SELECT CRIMINALSACCOUNT AS zhid,RZSJ AS sj,ZHLX,RZLX AS zmlx,RZZLX AS zlx,RZR AS jsr,RZJE AS srzc,'**' AS sjly FROM DZ_PERSON_INCOME WHERE SHZT = '**' ");     hql.append("UNION ALL ");     hql.append("SELECT CRIMINALSACCOUNT AS zhid,CZSJ AS sj,ZHLX,CZLX AS zmlx,CZZLX AS zlx,CZR AS jsr,CZJE AS srzc,'***' AS sjly FROM DZ_PERSON_OUTGOING ");     hql.append(")) srzc ");     hql.append("LEFT JOIN SW_DZ_ZFZH zfzh ON srzc.zhid = zfzh.id ");     hql.append("LEFT JOIN BS_CRIMINALINFO zf ON zfzh.zfid = zf.id ");     hql.append("LEFT JOIN (SELECT * FROM SW_DZ_LSYEJL WHERE TO_CHAR(rq,'yyyy-mm-dd') = ?1) ye ON zf.id = ye.zfid ");     hql.append("WHERE TO_CHAR(sj,'yyyy-mm-dd') >= ?2 AND TO_CHAR(sj,'yyyy-mm-dd') <= ?3 ");     if(StringUtils.isNotEmpty(zmlx)){         hql.append("AND zmlx = '"+zmlx+"'");     }         //....省略     if(StringUtils.isNotEmpty(zfxm)){         String[] list = zfxm.toString().split("[, ]");         if(list.length>1){             hql.append("AND (xm like '%"+list[0]+"%' or xm like '%"+list[1]+"%' or zhbh like '%"+list[1]+"%' or zhbh like '%"+list[1]+"%')");         }else{             hql.append("AND xm like '%"+list[0]+"%' or zhbh like '%"+list[0]+"%'");         }     }     if(StringUtils.isNotEmpty(queryParams.getSidx())){         switch(queryParams.getSidx()){             case "rq":             hql.append(" order by sj ");             break;             case ...//此處省略         }         hql.append(queryParams.getSord());     }else{         hql.append(" order by xm,sj desc");     }     //統計所有數據條數     String countSql = "select count(*) from ("+hql.toString()+")";     Query countQuery = em.createNativeQuery(countSql);     countQuery.setParameter(1, jzrq);     countQuery.setParameter(2, qsrq);     countQuery.setParameter(3, jzrq);     BigDecimal obj = (BigDecimal) countQuery.getSingleResult();     //得到頁數     int totalPage = (int) Math.ceil(obj.divide(new BigDecimal(queryParams.getRows())).doubleValue());     //分頁查詢     Query query = em.createNativeQuery(hql.toString());     int firstIndex = ((queryParams.getPage()-1) * queryParams.getRows());     query.setFirstResult(firstIndex);     query.setMaxResults(queryParams.getRows());     query.setParameter(1, jzrq);     query.setParameter(2, qsrq);     query.setParameter(3, jzrq);     List<Object> list = query.getResultList();     //設定輸出參數     JsonReader jsonReader = new JsonReader();     jsonReader.setPage(queryParams.getPage())     .setTotal(totalPage)     .setRecords(obj.intValue());     jsonReader.setRows(list);     return jsonReader; }

注意:在使用entitymanager進行自定義sql查詢的時候,如果遇到in條件你這樣設定參數將是錯誤的。

query.setParameter(1, jzrq);

javax.persistence.Query會在參數外圍用引號包圍起來 然后你的查詢就會變成 in ( 'xx,xx,xx')而不是你期望的in ('xx','xx','xx').

對於這種情況我們轉換思路,特殊處理:

//通過em獲取hibernate的session String sql = "SELECT nf,xx,xx FROM xx WHERE nf in (:nf)"; Session session = em.unwrap(Session.class); //獲取org.hibernate.Query Query query = session.createSQLQuery(sql+buffer); if(qjFields.contains(",")){     String[] qjs =  qjFields.split(",");     query.setParameterList("nf", qjs); }else{     query.setParameter("nf",qjFields); } //獲取數據 List<Object> rs = query.list();

以上就是關於Specification查詢的相關應用,其實 還有很多用法,這就看各位大俠們自行悟道了。歡迎喜歡springDataJpa的同道中人相互指導交流!

 


免責聲明!

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



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