详解Jpa动态复杂条件查询,查询指定字段、并包括sum、count、avg等数学运算 您所在的位置:网站首页 access创建参数查询条件设置含最低值 详解Jpa动态复杂条件查询,查询指定字段、并包括sum、count、avg等数学运算

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

2023-11-14 13:51| 来源: 网络整理| 查看: 265

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

效果展示

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

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

public interface PtActivityRepository extends JpaRepository, JpaSpecificationExecutor { }

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

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

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

private Page find(String states, String name, String begin, String end, Pageable pageable) { MySpecification 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的查询条件。如果哪个字段没传值,就忽略该筛选条件。

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

public void find() { MySpecification 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 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。

public interface JpaSpecificationExecutor { Optional findOne(@Nullable Specification var1); List findAll(@Nullable Specification var1); Page findAll(@Nullable Specification var1, Pageable var2); List findAll(@Nullable Specification var1, Sort var2); long count(@Nullable Specification var1); }public interface Specification extends Serializable { long serialVersionUID = 1L; static Specification not(Specification spec) { return Specifications.negated(spec); } static Specification where(Specification spec) { return Specifications.where(spec); } default Specification and(Specification other) { return Specifications.composed(this, other, CompositionType.AND); } default Specification or(Specification other) { return Specifications.composed(this, other, CompositionType.OR); } @Nullable Predicate toPredicate(Root var1, CriteriaQuery var2, CriteriaBuilder var3); }

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

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

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

public void findTemp() { ptActivityManager.findAll(new Specification() { @Override public Predicate toPredicate(Root 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,新手一看,比较茫然。下面就来解惑一下,这些都是什么鬼。

解析原生的底层查询

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

public List findResult(EntityManager entityManager, Class t) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createTupleQuery(); Root 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的封装就可以了,就能完成所有的单表复杂查询。

定义一个终极接口:

/** * 适用于对单表做sum、avg、count等运算时使用,并且查询条件不固定,需要动态生成predicate

* 如select sum(a), count(b), count distinct(c) from table where a = ? & b = ? * * @author wuweifeng wrote on 2018/1/3. */ public interface CriteriaQueryBuilder extends Specification { /** * 构建select字段 */ List> buildGroupBy(Root root); /** * 获取返回的结果集 */ List findResult(EntityManager entityManager, Class t); }

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

提供一个实现类:

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 implements CriteriaQueryBuilder { private List criterionList = new ArrayList(); private List selectorList = new ArrayList(); private List expressionList = new ArrayList(); @Override public List> selections = new ArrayList(); for (ISelector iSelector : selectorList) { selections.add(iSelector.getSelection(root, builder)); } return selections; } @Override public List> expressions = new ArrayList(); for (IExpression expression : expressionList) { expressions.add(expression.getGroupBy(root)); } return expressions; } @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { if (!criterionList.isEmpty()) { List 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 findResult(EntityManager entityManager, Class t) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createTupleQuery(); Root 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 void addAll(List selectors) { selectorList.addAll(selectors); } }

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

public void find() { NbQueryBuilder 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 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里找到联系方式索要代码。

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有