前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解Jpa动态复杂条件查询,查询指定字段、并包括sum、count、avg等数学运算,包括groupBy分组

详解Jpa动态复杂条件查询,查询指定字段、并包括sum、count、avg等数学运算,包括groupBy分组

作者头像
天涯泪小武
发布2021-12-09 19:17:46
3.9K0
发布2021-12-09 19:17:46
举报
文章被收录于专栏:SpringCloud专栏SpringCloud专栏

Jpa是我一直推荐在Springboot及微服务项目中使用的数据库框架,并由于官方的并不是十分友好和易用的api,导致很多人使用起来并不方便,下面就来展示一下我对api进行了封装后的代码。大大减轻了使用难度。

效果展示

首先我们直接来看最终的结果:

譬如有个entity叫PtActivity,它有一个Repository。

代码语言:javascript
复制
public interface PtActivityRepository extends JpaRepository<PtActivity, Long>,
        JpaSpecificationExecutor<PtActivity> {

}

继承了JpaSpecificationExecutor后,它拥有了这样一个方法:

代码语言:javascript
复制
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

即传入一个Specification对象,即可完成条件查询,来看一个简单的例子。MySpecification就是封装好的工具类,能够大幅简化jpa构建条件查询的操作。

代码语言:javascript
复制
  private Page<PtActivity> find(String states, String name, String begin, String end, Pageable pageable) {
        MySpecification<PtActivity> mySpecification = new MySpecification<>();
        String[] stateArray = states.split(",");

        if (begin != null) {
            mySpecification.add(Restrictions.gte("createTime", CommonUtil.beginOfDay(begin), true));
        }
        if (end != null) {
            mySpecification.add(Restrictions.lte("createTime", CommonUtil.endOfDay(end), true));
        }

        mySpecification.add(Restrictions.in("state", Arrays.asList(stateArray), true));
        mySpecification.add(Restrictions.like("name", name, true));
        mySpecification.add(Restrictions.eq("deleteFlag", false, true));
        return ptActivityManager.findAll(mySpecification, pageable);
    }

该demo构建了一个查询createTime大于begin,小于end,并且state字段的值,在某个数组范围内,并且name字段like一个传来的值,并且deleteFlag字段等于false的查询条件。如果哪个字段没传值,就忽略该筛选条件。

这样代码看起来就很容易理解,下面看一个稍微复杂点的例子:

代码语言:javascript
复制
public void find() {
        MySpecification<PtActivity> criteriaQueryBuilder = new MySpecification<>();
        criteriaQueryBuilder.addAll(Restrictions.pickSome("id","state"));
        //criteriaQueryBuilder.add(Restrictions.sum("id"));
        //criteriaQueryBuilder.add(Restrictions.max("state"));
        criteriaQueryBuilder.add(Restrictions.gte("createTime", CommonUtil.beginOfDay("2019-05-01"), true));
        criteriaQueryBuilder.add(Restrictions.lte("createTime", CommonUtil.endOfDay("2019-05-31"), true));

        //criteriaQueryBuilder.add(Restrictions.groupBy("state"));

        List<Tuple> tuples = criteriaQueryBuilder.findResult(em, PtActivity.class);
        for (Tuple tuple : tuples) {
            Object count = tuple.get(0);
            System.out.println(count);
        }
    }

该方法完成了只查询id、state字段,并且createTime在某个时间范围内的。如果把注释放开,就是查询sum(id),max(state) 并且groupBy state字段。

详细解析

何为Specification

还是回到Jpa的这个接口,可以看到,要完成一次查询,主要的工作就是构建Specification,而Specification接口中,主要就是一个方法即toPredicate方法。这个方法就是构建select * from table where xxxxx语句的where条件。其他的not、and都是对Specification的一些交集、并集,也就是where语句里的and、or。

代码语言:javascript
复制
public interface JpaSpecificationExecutor<T> {
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}
代码语言:javascript
复制
public interface Specification<T> extends Serializable {
    long serialVersionUID = 1L;

    static <T> Specification<T> not(Specification<T> spec) {
        return Specifications.negated(spec);
    }

    static <T> Specification<T> where(Specification<T> spec) {
        return Specifications.where(spec);
    }

    default Specification<T> and(Specification<T> other) {
        return Specifications.composed(this, other, CompositionType.AND);
    }

    default Specification<T> or(Specification<T> other) {
        return Specifications.composed(this, other, CompositionType.OR);
    }

    @Nullable
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}

我们可以这样理解,要做的一切事情,就是为了构建Predicate对象,该对象组合了N多个查询子语句。

所以我们要做的就是根据前端传来的字段构建多个Predicate对象,再将这多个Predicate组装成一个Predicate对象,就完成了条件查询的构建。

如果采用官方api来完成一次复杂条件查询,代码可能是下面这样的:

代码语言:javascript
复制
 public void findTemp() {
        ptActivityManager.findAll(new Specification<PtActivity>() {
            @Override
            public Predicate toPredicate(Root<PtActivity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder
                    criteriaBuilder) {
                Path idPath = root.get("id");
                Predicate predicate1 = criteriaBuilder.equal(idPath, "12345");

                Path statePath = root.get("state");
                Predicate predicate2 = criteriaBuilder.equal(statePath, "1");

                return criteriaBuilder.and(predicate1, predicate2);
            }
        });
    }

猛一看,其实还是挺乱的,什么root、criteriaQuery、criteriaBuilder都是些什么鬼,怎么组建的Predicate,新手一看,比较茫然。下面就来解惑一下,这些都是什么鬼。

解析原生的底层查询

事实上,要完成一次条件查询,它的流程是这样的:

代码语言:javascript
复制
public List<Tuple> findResult(EntityManager entityManager, Class<T> t) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
        Root<T> root = criteriaQuery.from(t);

        if (!selectorList.isEmpty()) {
            criteriaQuery.multiselect(buildSelections(criteriaBuilder, root));
        }
        if (!criterionList.isEmpty()) {
            criteriaQuery.groupBy(buildGroupBy(root));
        }
        criteriaQuery.where(toPredicate(root, criteriaQuery, criteriaBuilder));

        return entityManager.createQuery(criteriaQuery).getResultList();
    }

先获取EntityManager,然后从EntityManager中获取CriteriaBuilder,再从CriteriaBuilder中创建一个CriteriaQuery,然后将各个条件都组合到CriteriaQuery中,最终通过entityManager.createQuery(criteriaQuery).getResultList()来获取到查询结果。

譬如一次查询是这样的:select a, b, sum(c) from table where a > 0 and c < 1 group by a

那么a、b、sum(c)都属于CriteriaQuery中的select参数,where后面的条件都属于CriteriaQuery的where后的参数,groupBy和having都属于CriteriaQuery的对应的参数。最终组合成一个丰满的CriteriaQuery,并由EntityManager来createQuery并获取结果集。

可以看到里面有非常完整的构建的方法。我们要做的就是将select后面的组合成Selection对象,where后面的组合成Predicate对象,having、groupBy什么的按照属性类型组合即可。

这些Selection、Predicate对象怎么构建呢,就是靠CriteriaBuilder。

CriteriaBuilder里的箭头的方法,都是构建Selection的。

这几个都是构建Predicate的。

至于用来做having,groupBy的更简单,直接用root.get("字段名")就可以了。

知道了这些,问题就更简单了,我们要做的就是把构建这3个组合的方法给封装起来就好了。不然用上面官方提供的这些,很不方便。

JpaSpecificationExecutor怎么理解

我们知道,平时用这个findAll(Specification var1)时,只需要构建好Predicate即可。

里面的root,CriteriaQuery和builder都已经被Jpa赋值好了,我们只需要关注Predicate的构建,也就是说,这个findAll方法只能完成where条件的构建,而不能实现select后面属性的选择和groupBy的构建。

jpa怎么给root什么的赋值的呢,其实是这样的,Jpa是一种规范,Hibernate、OpenJPA对其进行了实现,譬如Springboot默认使用Hibernate实现Jpa,也就是上一小节提到的EntityManager那一套,Hibernate创建了CriteriaQuery和Builder和root,并且将值赋给上图的各参数中,供用户使用,来构建where条件需要的Predicate对象。

编码封装API

以上如果都理解了,那么就可以来编码了,我们做好构建Selection、Predicate、Expression的封装就可以了,就能完成所有的单表复杂查询。

定义一个终极接口:

代码语言:javascript
复制
/**
 * 适用于对单表做sum、avg、count等运算时使用,并且查询条件不固定,需要动态生成predicate</p>
 * 如select sum(a), count(b), count distinct(c) from table where a = ? & b = ?
 *
 * @author wuweifeng wrote on 2018/1/3.
 */
public interface CriteriaQueryBuilder<T> extends Specification<T> {

    /**
     * 构建select字段
     */
    List<Selection<?>> buildSelections(CriteriaBuilder builder, Root<T> root);

    /**
     * 构建groupBy字段
     */
    List<Expression<?>> buildGroupBy(Root<T> root);

    /**
     * 获取返回的结果集
     */
    List<Tuple> findResult(EntityManager entityManager, Class<T> t);
}

只要完成了这4个(包括Specification里的toPredicate)方法,就能从findResult里得到你想要的结果集。

提供一个实现类:

代码语言:javascript
复制
package com.maimeng.jd.global.specify;

import com.maimeng.jd.global.specify.simple.IExpression;
import com.maimeng.jd.global.specify.simple.IPredicate;
import com.maimeng.jd.global.specify.simple.ISelector;

import javax.persistence.EntityManager;
import javax.persistence.Tuple;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 定义一个查询条件容器,用于构建where条件、select字段、groupBy字段
 *
 * @author wuwf   on 17/6/6.
 */
public class NbQueryBuilder<T> implements CriteriaQueryBuilder<T> {
    private List<IPredicate> criterionList = new ArrayList<>();

    private List<ISelector> selectorList = new ArrayList<>();

    private List<IExpression> expressionList = new ArrayList<>();

    @Override
    public List<Selection<?>> buildSelections(CriteriaBuilder builder, Root<T> root) {
        List<Selection<?>> selections = new ArrayList<>();
        for (ISelector iSelector : selectorList) {
            selections.add(iSelector.getSelection(root, builder));
        }
        return selections;
    }

    @Override
    public List<Expression<?>> buildGroupBy(Root<T> root) {
        List<Expression<?>> expressions = new ArrayList<>();
        for (IExpression expression : expressionList) {
            expressions.add(expression.getGroupBy(root));
        }
        return expressions;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        if (!criterionList.isEmpty()) {
            List<Predicate> predicates = new ArrayList<>();
            for (IPredicate c : criterionList) {
                predicates.add(c.toPredicate(root, criteriaBuilder));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        }
        return criteriaBuilder.conjunction();
    }

    @Override
    public List<Tuple> findResult(EntityManager entityManager, Class<T> t) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
        Root<T> root = criteriaQuery.from(t);

        if (!selectorList.isEmpty()) {
            criteriaQuery.multiselect(buildSelections(criteriaBuilder, root));
        }
        if (!criterionList.isEmpty()) {
            criteriaQuery.groupBy(buildGroupBy(root));
        }
        criteriaQuery.where(toPredicate(root, criteriaQuery, criteriaBuilder));

        return entityManager.createQuery(criteriaQuery).getResultList();
    }

    /**
     * 增加简单条件表达式
     */
    public void add(ISelector iSelector) {
        if (iSelector != null) {
            selectorList.add(iSelector);
        }
    }

    /**
     * 增加where子语句
     */
    public void add(IPredicate iPredicate) {
        if (iPredicate != null) {
            criterionList.add(iPredicate);
        }
    }

    /**
     * 增加尾部语句
     */
    public void add(IExpression iExpression) {
        if (iExpression != null) {
            expressionList.add(iExpression);
        }
    }

    public <R extends ISelector> void addAll(List<R> selectors) {
        selectorList.addAll(selectors);
    }
}

最终在service里使用起来就是这样的:

代码语言:javascript
复制
 public void find() {
        NbQueryBuilder<PtActivity> criteriaQueryBuilder = new NbQueryBuilder<>();
        criteriaQueryBuilder.addAll(Restrictions.pickSome("id", "state"));
        //criteriaQueryBuilder.add(Restrictions.sum("id"));
        //criteriaQueryBuilder.add(Restrictions.max("state"));
        criteriaQueryBuilder.add(Restrictions.gte("createTime", CommonUtil.beginOfDay("2019-05-01"), true));
        criteriaQueryBuilder.add(Restrictions.lte("createTime", CommonUtil.endOfDay("2019-05-31"), true));

        //criteriaQueryBuilder.add(Restrictions.groupBy("state"));

        List<Tuple> tuples = criteriaQueryBuilder.findResult(em, PtActivity.class);
        for (Tuple tuple : tuples) {
            Object count = tuple.get(0);
            System.out.println(count);
        }
    }

当然,如果你不需要构建Selection、groupBy时,也可以只构建Predicate,然后使用jpa的findAll()方法即可。

代码结构如下,都是一些对构建条件的封装和一个Restrictions的工厂类。

由于代码很多,我就不一一贴了。能理解全文的,自己应该也能写出来。

写不出来的,可以去我的开源区块链平台项目https://gitee.com/tianyalei/md_blockchain里找到联系方式索要代码。

需注意,该封装,是针对于单表用的,并没有对多表联合查询做封装,因为我从来只有单表操作,从不做任何外键以及多表级联查询。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 效果展示
  • 详细解析
    • 何为Specification
      • 解析原生的底层查询
        • JpaSpecificationExecutor怎么理解
          • 编码封装API
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档