Spring Data JPA支持JPA2.0的Criteria查詢,相應的接口是JpaSpecificationExecutor。Criteria 查詢:是一種類型安全和更面向對象的查詢 。 java
這個接口基本是圍繞着Specification接口來定義的, Specification接口中只定義了以下一個方法: spring
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); sql
要理解這個方法,以及正確的使用它,就須要對JPA2.0的Criteria查詢有一個足夠的熟悉和理解,由於這個方法的參數和返回值都是JPA標準裏面定義的對象。 json
Criteria查詢基本概念 安全
Criteria 查詢是以元模型的概念爲基礎的,元模型是爲具體持久化單元的受管實體定義的,這些實體能夠是實體類,嵌入類或者映射的父類。 session
CriteriaQuery接口:表明一個specific的頂層查詢對象,它包含着查詢的各個部分,好比:select 、from、where、group by、order by等注意:CriteriaQuery對象只對實體類型或嵌入式類型的Criteria查詢起做用 app
Root接口:表明Criteria查詢的根對象,Criteria查詢的查詢根定義了實體類型,能爲未來導航得到想要的結果,它與SQL查詢中的FROM子句相似 框架
1:Root實例是類型化的,且定義了查詢的FROM子句中可以出現的類型。 ide
2:查詢根實例能經過傳入一個實體類型給 AbstractQuery.from方法得到。 ui
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的同道中人相互指導交流!