前言
在剛開始學習的時候,在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; }