当Repository接口继承了JpaSpecificationExecutor后,我们就可以使用如下接口进行分页查询:

    /**
* Returns a {@link Page} of entities matching the given {@link Specification}.
*
* @param spec can be {@literal null}.
* @param pageable must not be {@literal null}.
* @return never {@literal null}.
*/
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

结合jpa-spec可以很容易构造出Specification:

jpa-spec github地址:https://github.com/wenhao/jpa-spec

public Page<Person> findAll(SearchRequest request) {
Specification<Person> specification = Specifications.<Person>and()
.eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
.gt(Objects.nonNull(request.getAge()), "age", 18)
.between("birthday", new Date(), new Date())
.like("nickName", "%og%", "%me")
.build(); return personRepository.findAll(specification, new PageRequest(0, 15));
}

单表查询确实很简单,但对复杂查询,就复杂上些了:

public List<Phone> findAll(SearchRequest request) {
Specification<Phone> specification = Specifications.<Phone>and()
.eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei")
.eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack")
.build(); return phoneRepository.findAll(specification);
}

这里主表是phone,使用了person的name做条件,使用方法是person.name。

jpa-spec内部会分析person.name,如下代码:

public From getRoot(String property, Root<T> root) {
if (property.contains(".")) {
String joinProperty = StringUtils.split(property, ".")[0];
return root.join(joinProperty, JoinType.LEFT);
} else {
return root;
}
}

就可看到它用了root.join,那就有一个问题,如果有两个person字段的条件,那就要再join一次,就会生成这样的sql:

select * from phone left outer join person on XX=XX left outer join person XX=XX.

这样肯定不满足需求。这应该也是jpa-spec的一个bug吧

为了解决这个问题,可以使用它提供的另一种方式查询:

public List<Phone> findAll(SearchRequest request) {
Specification<Person> specification = Specifications.<Person>and()
.between("age", 10, 35)
.predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
Join address = root.join("addresses", JoinType.LEFT);
return cb.equal(address.get("street"), "Chengdu");
}))
.build(); return phoneRepository.findAll(specification);
}

这要就可以解决大多数情况了,除了分页

看下正常的单表分页+排序查询:

public Page<Person> findAll(SearchRequest request) {
Specification<Person> specification = Specifications.<Person>and()
.eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
.gt("age", 18)
.between("birthday", new Date(), new Date())
.like("nickName", "%og%")
.build(); Sort sort = Sorts.builder()
.desc(StringUtils.isNotBlank(request.getName()), "name")
.asc("birthday")
.build(); return personRepository.findAll(specification, new PageRequest(0, 15, sort));
}

如果在此基础上增加关联,如下代码:

public Page<Person> findAll(SearchRequest request) {
Specification<Person> specification = Specifications.<Person>and()
.predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
Join address = root.join("addresses", JoinType.LEFT);
return cb.equal(address.get("street"), "Chengdu");
}))
.eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
.gt("age", 18)
.between("birthday", new Date(), new Date())
.like("nickName", "%og%")
.build(); Sort sort = Sorts.builder()
.desc(StringUtils.isNotBlank(request.getName()), "name")
.asc("birthday")
.build(); return personRepository.findAll(specification, new PageRequest(0, 15, sort));
}

就会发现addresses的延迟加载失效,生成很多查询addresses的语句,解决方案如下:

public Page<Person> findAll(SearchRequest request) {
Specification<Person> specification = Specifications.<Person>and()
.predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
Join address;
if (Long.class != query.getResultType()) {
address = (Join) root.fetch("addresses", JoinType.LEFT);
} else {
address = root.join("addresses", JoinType.LEFT);
}
return cb.equal(address.get("street"), "Chengdu");
}))
.eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
.gt("age", 18)
.between("birthday", new Date(), new Date())
.like("nickName", "%og%")
.build(); Sort sort = Sorts.builder()
.desc(StringUtils.isNotBlank(request.getName()), "name")
.asc("birthday")
.build(); return personRepository.findAll(specification, new PageRequest(0, 15, sort));
}

至此,用Specification查询就应该够用了,再配合JpaRepository (SimpleJpaRepository)提供的方法 和@Query注解方法,和criteria api查询,这四种JPA查询就可以解决大多数应用问题了。

spring data jpa Specification 复杂查询+分页查询的更多相关文章

  1. SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法

    软件152 尹以操 首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml ...

  2. spring data jpa 使用方法命名规则查询

    按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写.框架在进行方法名解析时,会先把方法名多余的前缀 ...

  3. 【hql】spring data jpa中 @Query使用hql查询 问题

    spring data jpa中 @Query使用hql查询 问题 使用hql查询, 1.from后面跟的是实体类 不是数据表名 2.字段应该用实体类中的字段 而不是数据表中的属性 实体如下 hql使 ...

  4. Spring Data JPA 复杂/多条件组合查询

    1: 编写DAO类或接口  dao类/接口 需继承 public interface JpaSpecificationExecutor<T> 接口: 如果需要分页,还可继承 public ...

  5. Spring Data JPA 实现多表关联查询

    本文地址:https://liuyanzhao.com/6978.html 最近抽出时间来做博客,数据库操作使用的是 JPA,相对比 Mybatis 而言,JPA 单表操作非常方便,增删改查都已经写好 ...

  6. spring data jpa 使用JPQL的方式查询

    用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Que ...

  7. spring data jpa Specification动态查询

    package com.ytkj.entity; import javax.persistence.*; import java.io.Serializable; /** * @Entity * 作用 ...

  8. 【spring data jpa】带有条件的查询后分页和不带条件查询后分页实现

    一.不带有动态条件的查询 分页的实现 实例代码: controller:返回的是Page<>对象 @Controller @RequestMapping(value = "/eg ...

  9. Spring data JPA先排序再分页。

    //工具类,增删改查等等package com.yunqing.service.impl; import java.util.Map; import org.springframework.beans ...

随机推荐

  1. yarn与npm对比

    https://www.jianshu.com/p/254794d5e741(copy)

  2. ICPC — International Collegiate Programming Contest Asia Regional Contest, Yokohama, 2018–12–09 题解

    目录 注意!!此题解存在大量假算法,请各位巨佬明辨! Problem A Digits Are Not Just Characters 题面 题意 思路 代码 Problem B Arithmetic ...

  3. 【BZOJ3931】[CQOI2015]网络吞吐量

    Description 路由是指通过计算机网络把信息从源地址传输到目的地址的活动,也是计算机网络设计中的重点和难点.网络中实现路由转发的硬件设备称为路由器.为了使数据包最快的到达目的地,路由器需要选择 ...

  4. centos7 修改ali yum源

    centos7 修改yum源为阿里源,某下网络下速度比较快 首先是到yum源设置文件夹里 安装base reop源 cd /etc/yum.repos.d 接着备份旧的配置文件 sudo mv Cen ...

  5. node、npm、git版本升级

    node版本升级: npm install -g n 或者 npm i -g n --force n stable或者n --stable:安装最近稳定版本 n latest或者n --latest: ...

  6. Mac OS下Flutter环境搭建记录,VS Code开发

    安装Flutter 获取FlutterSDK 终端cd进入SDK安装目录,比如 cd ~/FlutterSDK 由于在国内访问Flutter有时可能会受到限制,Flutter官方为中国开发者搭建了临时 ...

  7. Python基本语法_基本数据类型_序列类型详解

    目录 目录 序列 序列的标准操作符 切片操作符 一个例子 字符串的连接 序列的功能函数 enumerate 枚举出序列对象的元素 len 获取序列对象的长度 min 取出sequence中的最小值 m ...

  8. Jedis源码浅析

    1.概述 Jedis是redis官网推荐的redis java client,代码维护在github https://github.com/xetorthio/jedis. 本质上Jedis帮我们封装 ...

  9. Unity3D-Canvas-UIScaleMode

    UnityCanvasUI自适应部分 用Canvas下的自适应UI Scale Mode: Constant Pixel Size:固定像素大小,无论如何UI怎么样就怎么样,只能通过 Scale Fa ...

  10. shell脚本批量开启防火墙端口

    # 注意:shell脚本批量执行命令,不能只写一个函数,然后把所有命令复制进去,之前试过这样是不行的.必须要有一个判断命令执行成功与否的语句 # 简单的命令可以不加结果判断符号,但是遇到解压包.sed ...