前言
在刚开始学习的时候,在dao的定义的接口需要继承JpaRepository<T, ID>接口和JpaSpecificationExecutor< T >接口,但是一直以来我用到的都只是JpaRepository,用于自动生成相关SQL语句简化代码。而JpaSpecificationExecutor给我的感觉就可有可无了,直到最近才发现它的用处,在此记录一波。因为是学习笔记的关系,所以里面都只是截取关键的代码进行记录。
正文
要想使用Spring Data JPA,需要在pom.xml中添加以下依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependencies>
如前言所说,带条件的分页查询方法是被定义在JpaSpecificationExecutor接口中的,所以这里需要继承这个接口。
/** * @author Veggie * @date 2019/8/14 - 14:11 */ @Repository public interface MessageRepository extends JpaRepository<Message, Long>, JpaSpecificationExecutor<Message> { }
1. 条件查询
自定义查询条件的步骤:
- 实现Specification< T >接口(提供泛型;查询的对象类型)
- 实现toPredicate方法(构造查询条件)
- 需要借助方法参数中的两个参数:
root:获取需要查询的对象属性
CriteriaBuilder:构造查询条件,内部封装了很多查询条件(模糊匹配,精准匹配)
//自定义查询条件 Specification<Message> spec = new Specification<Message>() { @Override public Predicate toPredicate(Root<Message> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { //根据属性名获取查询对象的属性 Path<Message> path = root.get("receiverName"); //相当于 where receiverName = "Veggie", CriteriaBuilder接口中还有很多查询条件,建议看源码 Predicate equal = criteriaBuilder.equal(path, "Veggie"); return equal; } } //进行条件查询,findAll()方法中的参数即为条件 List<Message> result = MessageRepository.findAll(spec);
当查询条件用到gt, ge, lt, le, like(分别表示>, >=, <, <=,模糊查询)时,需要表明查询对象属性的类别,如下所示:
//查询用户名以"V"开头的用户 Predicate like = criteriaBuilder.like(path.as(String.class), "V%");
2. 排序
排序用到的是一个Sort类,它是查询的排序选项(看源码,有介绍)
它初始化是用到的参数第一个参数:
Sort.Direction.DESC表示降序
Sort.Direction.ASC表示升序
随后的参数就是要排序的属性列表,可以有多个参数,也可以直接用List传,但是至少传入一个属性。
Sort sort = new Sort(Sort.Direction.DESC, "id"); //sort作为findAll()方法中的参数,查询得到得到的结果是经过排序的 List<Message> result = MessageRepository.findAll(sort);
3.分页查询
分页需要设置分页参数类Pageable,初始化主要是有两个参数:第一个是查询的页码(下标从0开始),第二个是每页查询的条数,比如说结果有55条,如果每页查询10条,结果就会被分成6页。也可以添加
注意:原来用到的new PageRequest()已经过时,现在用PageRequest.of()来实现。
Page接口是封装为Spring Data Jpa 内部的page bean,它的常用方法如下:
//获取总页数 int getTotalPages(); //获取总记录数 long getTotalElements(); //获取列表数据 List<T> getContent();
分页查询的代码如下:
//设置分页参数 Pageable pageable = PageRequest.of(0,5); //分页查询 Page<Message> page = MessageRepository.findAll(pageable);
- 1
- 2
- 3
- 4
4. 完整的方法代码
完整的代码是以上三个知识点的集合,是带条件的分页查询,查询得到的结果按id号降序排序。
@RequestMapping(path = "/page") public List<Message> queryPage(@RequestBody Map<String, Object> params) { /** * 自定义查询条件 * 1. 实现Specification接口(提供泛型;查询的对象类型) * 2. 实现toPredicate方法(构造查询条件) * 3. 需要借助方法参数中的两个参数( * root:获取需要查询的对象属性 * CriteriaBuilder:构造查询条件,内部封装了很多查询条件(模糊匹配,精准匹配) */ Specification<Message> spec = new Specification<Message>() { @Override public Predicate toPredicate(Root<Message> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { Path<Message> path = root.get("receiverName"); Predicate equal = criteriaBuilder.equal(path, "Veggie"); return equal; } }; Integer pageNo = Integer.valueOf((String) params.get("pageNo")); Integer pageSize = Integer.valueOf((String)params.get("pageSize")); /** * 添加排序Sort * Sort.Direction.DESC表示降序 * Sort.Direction.ASC表示升序 * properties是指实体类的属性名(不是字段名) */ Sort sort = new Sort(Sort.Direction.ASC, "id"); /** * 分页参数Pageable * 参数1:查询的页码 * 参数2:每页查询的条数 * 参数3:查询结果的排序规则(可选 */ Pageable pageable = PageRequest.of(pageNo, pageSize, sort); //原来的new PageRequest()已经过时 /** * 分页查询 * 参数1:查询条件Specification * 参数2:分页参数Pageable */ Page<Message> page = MessageRepository.findAll(spec, pageable); return page.getContent(); }
感悟
既然都写博客了,顺带记录最近学习以及和大佬交谈的感悟:
- 理解概念:在学习的时候,不能仅仅想着敲代码,要多关注一些相关概念的理解,就比如说JPA、 Hibernate和SpringData JPA各自的基本概念,他们之间有什么关系之类的。
- 多看源码:我具体也说不上来,就本能的感觉这个很重要。有时候一看源码,可以理解之前想了很久都没有想明白的问题。其次,看某个类或接口的源码时,还可以了解到这个类可以提供什么方法(一般都会有英文注释的)。
- 思维导图:就像看书一定要想看目录,学习新的内容应该先做个思维导图。它以让我们对一样东西的总体有比较直观、系统的认识,可以让学习的思路变得清晰,可以避免浪费一些不必要 的时间。
- 多做笔记:连dalao都记不住之前学过的东西,何况我等凡夫俗子。做笔记不仅可以备忘,避免踩同样的坑,还可以加深对该部分知识的理解。
情有独钟的JPA
平时在写一些小项目时,比较喜欢引用 Spring Data Jpa,其实还是图他写代码快~
在日常的开发工作中,分页列表查询基本是随处可见,下面一起看一下如何使用 jpa 进行多条件查询以及查询列表分页呢?
关于JPA的使用
关于 jpa 的使用,下面2步简单过一下,详细资料,小伙伴自行搜索一下吧~
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
mysql、web、druid......
</dependency>
2、配置yml
图方便直接贴代码了:
spring:
# 数据源
datasource:
url: jdbc:mysql://127.0.0.1:3306/tmax?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 1234
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
jpa:
# 操作数据库时显示sql语句
show-sql: true
# 自动生成表结构
generate-ddl: true
hibernate:
ddl-auto: none
database-platform: org.hibernate.dialect.MySQL57Dialect
分页查询
我们了解 jpa 基本是不用去写 sql 的,继承 JpaRepository 即可,同样也提供给了我们分页查询的方法,「补充:使用分页需要同时继承JpaSpecificationExecutor」
举例:
Page<VideoCategory> findByCondition(SearchVo searchVo, Pageable pageable);
通过传入一个遵循 pageale 协议的对象来获取某一页的数据,通过源码查看,发现 Pageable 是一个接口,提供了分页一组方法的声明,如第几页,每页多少条记录,排序信息等,部分方法如下:
int getPageNumber();
int getPageSize();
int getOffset();
Sort getSort();
Pageable next();
Pageable previousOrFirst();
Pageable first();
boolean hasPrevious();
通过这些方法我们可以构造我们的 pageable 对象,需要注意的是 jpa 在构造页码初始时,是从 0 开始的。
废话不多说,来看一段代码吧:
1. impl
@Override
public Page<VideoCategory> findByCondition(VideoCategory videoCategory, SearchVo searchVo, Pageable pageable) {
return videoCategoryDao.findAll(new Specification<VideoCategory>() {
@Nullable
@Override
public Predicate toPredicate(Root<VideoCategory> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
/** 可添加你的其他搜索过滤条件 默认已有创建时间过滤 **/
Path<Date> createTimeField=root.get("createTime");
Path<String> categoryIdField=root.get("categoryId");
List<Predicate> list = new ArrayList<Predicate>();
/** 创建时间 **/
if(StrUtil.isNotBlank(searchVo.getStartDate())&&StrUtil.isNotBlank(searchVo.getEndDate())){
Date start = DateUtil.parse(searchVo.getStartDate());
Date end = DateUtil.parse(searchVo.getEndDate());
list.add(cb.between(createTimeField, start, DateUtil.endOfDay(end)));
}
/** 视频分类 **/
if(StrUtil.isNotBlank(videoCategory.getCategoryId())){
list.add(cb.equal(categoryIdField,videoCategory.getCategoryId()));
}
Predicate[] arr = new Predicate[list.size()];
cq.where(list.toArray(arr));
return null;
}
}, pageable);
}
2. controller
@RequestMapping(value = "/getByCondition", method = RequestMethod.GET)
@ApiOperation(value = "多条件分页获取")
public Result<Page<VideoCategory>> getByCondition(
@ModelAttribute VideoCategory videoCategory,
@ModelAttribute SearchVo searchVo,
@ModelAttribute PageVo pageVo){
Page<VideoCategory> page = videoCategoryService.findByCondition(videoCategory, searchVo, PageUtil.initPage(pageVo));
return new ResultUtil<Page<VideoCategory>>().setData(page);
}
3. PageUtil
public static Pageable initPage(PageVo page){
Pageable pageable = null;
int pageNumber = page.getPageNumber();
int pageSize = page.getPageSize();
String sort = page.getSort();
String order = page.getOrder();
if(pageNumber<1){
pageNumber = 1;
}
if(pageSize<1){
pageSize = 10;
}
if(StrUtil.isNotBlank(sort)) {
Sort.Direction d;
if(StrUtil.isBlank(order)) {
d = Sort.Direction.DESC;
} else {
d = Sort.Direction.valueOf(order.toUpperCase());
}
Sort s = new Sort(d, sort);
pageable = PageRequest.of(pageNumber-1, pageSize, s);
} else {
pageable = PageRequest.of(pageNumber-1, pageSize);
}
return pageable;
}
link: https://www.cnblogs.com/niceyoo/p/10817290.html
Page<WorkWeight> page = workWeightRepository.findAll(new Specification<WorkWeight>() { @Override public Predicate toPredicate(Root<WorkWeight> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> list = new ArrayList<>(); if(null != subDepartmentId && !"".equals("subDepartmentId")){ list.add(cb.equal(root.<String>get("subDepartmentId"), subDepartmentId)); } if(null != status && !"".equals("status")){ list.add(cb.equal(root.<String>get("status"), status)); } if (null != startTime && !"".equals("startTime")) { list.add(cb.greaterThanOrEqualTo(root.<Date>get("startTime"), DateUtil.stringToDate(startTime))); } if (null != endTime && !"".equals("endTime")) { list.add(cb.lessThanOrEqualTo(root.<Date>get("endTime"), DateUtil.stringToDate(endTime))); } Expression<String> exp = root.<String>get("departmentId"); list.add(exp.in(idList)); // 往in中添加所有id 实现in 查询 if (list.size() != 0) { Predicate[] p = new Predicate[list.size()]; return cb.and(list.toArray(p)); } else { return null; } } }, new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort()));
@Override public Page<泛型> findRecordList(int couponDetailId, int pageNum, int pageSize, String startTime, String endTime) { try { //排序规则和分页 Sort sort = new Sort(new Sort.Order(Sort.Direction.DESC, "createTime")); PageRequest pageRequest = new PageRequest(pageNum - 1, pageSize, sort); Specification specification = new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { //增加筛选条件 Predicate predicate = cb.conjunction(); predicate.getExpressions().add(cb.equal(root.get("cardId"), couponDetailId)); //起始日期 if (startTime != null && !startTime.trim().equals("")) { predicate.getExpressions().add(cb.greaterThanOrEqualTo(root.get("createTime").as(String.class), startTime)); } //结束日期 if (endTime != null && !endTime.trim().equals("")) { predicate.getExpressions().add(cb.lessThanOrEqualTo(root.get("createTime").as(String.class), endTime)); } return predicate; } }; Page all = discountCouponRecordDao.findAll(specification, pageRequest); return all; }