SpringDataJpa的Specification查询

来源:互联网 发布:mac本终端安装telnet 编辑:程序博客网 时间:2024/05/16 00:50
摘要: 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的使用

@PersistenceContextprivate 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的sessionString sql = "SELECT nf,xx,xx FROM xx WHERE nf in (:nf)";Session session = em.unwrap(Session.class);//获取org.hibernate.QueryQuery 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查询的相关应用,其实 还有很多用法,这就看各位大侠们自行悟道了。

备注:转载自:https://my.oschina.net/u/2434456/blog/596938

0 0
原创粉丝点击