0
点赞
收藏
分享

微信扫一扫

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


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

效果展示

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

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

public interface PtActivityRepository extends JpaRepository<PtActivity, Long>,
JpaSpecificationExecutor<PtActivity> {

}

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

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

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

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的查询条件。如果哪个字段没传值,就忽略该筛选条件。

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

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。

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);
}

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来完成一次复杂条件查询,代码可能是下面这样的:

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,新手一看,比较茫然。下面就来解惑一下,这些都是什么鬼。

解析原生的底层查询

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

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并获取结果集。

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

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

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

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

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

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

这几个都是构建Predicate的。

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

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

JpaSpecificationExecutor怎么理解

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

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

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

里面的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</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里得到你想要的结果集。

提供一个实现类:

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里使用起来就是这样的:

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的工厂类。

详解Jpa动态复杂条件查询,查询指定字段、并包括sum、count、avg等数学运算,包括groupBy分组_jpa高级使用方法_06

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

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

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

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

 

 

 

举报

相关推荐

0 条评论