1. 程式人生 > 程式設計 >SpringBoot系列教程JPA之query使用姿勢詳解之基礎篇

SpringBoot系列教程JPA之query使用姿勢詳解之基礎篇

前面的幾篇文章分別介紹了CURD中的增刪改,接下來進入最最常見的查詢篇,看一下使用jpa進行db的記錄查詢時,可以怎麼玩

本篇將介紹一些基礎的查詢使用姿勢,主要包括根據欄位查詢,and/or/in/like/between 語句,數字比較,排序以及分頁

I. 環境準備

在開始之前,當然得先準備好基礎環境,如安裝測試使用mysql,建立SpringBoot專案工程,設定好配置資訊等,關於搭建專案的詳情可以參考前一篇文章

下面簡單的看一下演示新增記錄的過程中,需要的配置

1. 表準備

沿用前一篇的表,結構如下

CREATE TABLE `money`
( `id` int(11) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL DEFAULT '' COMMENT '使用者名稱',`money` int(26) NOT NULL DEFAULT '0' COMMENT '錢',`is_deleted` tinyint(1) NOT NULL DEFAULT '0',`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',`update_at` timestamp NOT NULL DEFAULT
CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',PRIMARY KEY (`id`),KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 複製程式碼

2. 專案配置

配置資訊,與之前有一點點區別,我們新增了更詳細的日誌列印;本篇主要目標集中在新增記錄的使用姿勢,對於配置說明,後面單獨進行說明

## DataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=
## jpa相關配置
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
複製程式碼

3. 資料準備

資料修改嘛,所以我們先向表裡面插入兩條資料,用於後面的操作

INSERT INTO `money` (`id`,`name`,`money`,`is_deleted`,`create_at`,`update_at`)
VALUES
	(1,'一灰灰blog',100,0,'2019-04-18 17:01:40','2019-04-18 17:01:40'),(2,'一灰灰2',200,(3,'一灰灰3',300,(4,'一灰灰4',400,(5,'一灰灰5',500,(6,'Batch 一灰灰blog',(7,'Batch 一灰灰blog 2',(8,'Batch 一灰灰 3',(9,'Batch 一灰灰 4',(10,'batch 一灰灰5',1498,'2019-04-18 17:01:58'),(11,'batch 一灰灰6',(12,'batch 一灰灰7',(13,'batch 一灰灰8','2019-04-18 17:01:40');
複製程式碼

db

II. Query基本使用姿勢

下面進入簡單的查詢操作姿勢介紹,單表的簡單and/or/in/compare查詢方式

1. 表關聯POJO

查詢返回的記錄與一個實體類POJO進行繫結,藉助前面的分析結果,如下

@Data
@DynamicUpdate
@DynamicInsert
@Entity
@Table(name = "money")
public class MoneyPO {
    @Id
    // 如果是auto,則會報異常 Table 'mysql.hibernate_sequence' doesn't exist
    // @GeneratedValue(strategy = GenerationType.AUTO)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "money")
    private Long money;

    @Column(name = "is_deleted")
    private Byte isDeleted;

    @Column(name = "create_at")
    @CreatedDate
    private Timestamp createAt;

    @Column(name = "update_at")
    @CreatedDate
    private Timestamp updateAt;

}
複製程式碼

上面類中的幾個註解,說明如下

  • @Data 屬於lombok註解,與jpa無關,自動生成getter/setter/equals/hashcode/tostring等方法
  • @Entity,@Table jpa註解,表示這個類與db的表關聯,具體匹配的是表 money
  • @Id @GeneratedValue 作用與自增主鍵
  • @Column表明這個屬性與表中的某列對應
  • @CreateDate根據當前時間來生成預設的時間戳

2. Repository API宣告

接下來我們新建一個api繼承自CurdRepository,然後通過這個api來與資料庫打交道,後面會在這個類中新增較多的查詢方法

public interface MoneyBaseQueryRepository extends CrudRepository<MoneyPO,Integer> {
}
複製程式碼

3. 使用姿勢

a. 根據id查詢

CrudRepository已經提供的功能,根據主鍵id進行查詢,對於使用者而言,沒有什麼需要額外操作的,直接訪問即可

private void queryById() {
    // 根據主鍵查詢,直接使用介面即可
    Optional<MoneyPO> res = moneyCurdRepository.findById(1);
    System.out.println("queryById return: " + res.get());
}
複製程式碼

b. 根據欄位查詢

除了根據主鍵查詢,實際的業務場景中,根據某個欄位進行查詢的case,簡直不要更多,在jpa中可以怎麼做呢?

  • Repository介面中宣告一個方法,命名規則為
  • findByXXX 或者 queryByXXX (注意這裡的xxx用POJO中的成員名替換,表示根據這個成員進行查詢)

一個簡單的case,如果我希望實現根據name進行查詢,那麼在MoneyBaseQueryRepository中新增下面兩個方法中的任意一個都可以

/**
 * 根據使用者名稱查詢
 *
 * @param name
 * @return
 */
List<MoneyPO> findByName(String name);

List<MoneyPO> queryByName(String name);
複製程式碼

如果需要多個成員的查詢呢?也簡單,形如findByXxxAndYyyy相當於sql中的where xxxx=? and yyy=?

如我們也可以增加下面兩個方法(一個and、一個or查詢)

/**
 * 根據使用者名稱 + money查詢
 *
 * @param name
 * @param money
 * @return
 */
List<MoneyPO> findByNameAndMoney(String name,Long money);


/**
 * 根據使用者名稱 or id查詢
 *
 * @param name
 * @param id
 * @return
 */
List<MoneyPO> findByNameOrId(String name,Integer id);
複製程式碼

一個簡單的測試case可以如下

private void queryByField() {
    // 根據內部成員進行查詢,需要自己定義新的介面
    String name = "一灰灰blog";
    Iterable<MoneyPO> res = moneyCurdRepository.findByName(name);
    System.out.println("findByName return: " + res);

    res = moneyCurdRepository.queryByName(name);
    System.out.println("queryByName return: " + res);

    Long money = 100L;
    res = moneyCurdRepository.findByNameAndMoney(name,money);
    System.out.println("findByNameAndMoney return: " + res);

    Integer id = 5;
    res = moneyCurdRepository.findByNameOrId(name,id);
    System.out.println("findByNameOrId return: " + res);
}
複製程式碼

執行之後輸出結果如下,下面也包括了對應的sql,便於理解

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.create_at as create_a2_0_,moneypo0_.is_deleted as is_delet3_0_,moneypo0_.money as money4_0_,moneypo0_.name as name5_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=?
findByName return: [MoneyPO(id=1,name=一灰灰blog,money=100,isDeleted=0,createAt=2019-04-18 17:01:40.0,updateAt=2019-04-18 17:01:40.0)]
-------- 人工拆分 -----------
Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=?
queryByName return: [MoneyPO(id=1,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=? and moneypo0_.money=?
findByNameAndMoney return: [MoneyPO(id=1,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=? or moneypo0_.id=?
findByNameOrId return: [MoneyPO(id=1,updateAt=2019-04-18 17:01:40.0),MoneyPO(id=5,name=一灰灰5,money=500,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

c. like查詢

上面的查詢方式為等值查詢,當在sql中除了等值查詢(即=查詢)之外,還有各種比較查詢,不等查詢以及like語句,在jpa中也比較簡單,在repository定義的方法名,加一個like即可

/**
 * like查詢
 *
 * @param name
 * @return
 */
List<MoneyPO> findByNameLike(String name);
複製程式碼

使用的時候,需要稍微注意一下,根據實際情況決定要不要加上 '%'

private void queryByLike() {
    // like 語句查詢
    String name = "一灰灰%";
    Iterable<MoneyPO> res = moneyCurdRepository.findByNameLike(name);
    System.out.println("findByName like: " + res);
}
複製程式碼

輸出結果為

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name like ?
findByName like: [MoneyPO(id=1,MoneyPO(id=2,name=一灰灰2,money=200,MoneyPO(id=3,name=一灰灰3,money=300,MoneyPO(id=4,name=一灰灰4,money=400,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

d. in查詢

對於in查詢,CurdRepository提供了根據主鍵id的查詢方式,直接呼叫findAllById即可,如果是其他的,可以通過宣告一個介面的方式來支援

/**
 * in查詢
 *
 * @param moneys
 * @return
 */
List<MoneyPO> findByMoneyIn(List<Long> moneys);
複製程式碼

測試case如下

// in 查詢
List<Integer> ids = Arrays.asList(1,2,3);
Iterable<MoneyPO> res = moneyCurdRepository.findAllById(ids);
System.out.println("findByIds return: " + res);

res = moneyCurdRepository.findByMoneyIn(Arrays.asList(400L,300L));
System.out.println("findByMoneyIn return: " + res);
複製程式碼

輸出結果

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id in (?,?,?)
findByIds return: [MoneyPO(id=1,updateAt=2019-04-18 17:01:40.0)]
------ 手動拆分 ----------
Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.money in (?,?)
findByMoneyIn return: [MoneyPO(id=3,MoneyPO(id=12,name=batch 一灰灰7,MoneyPO(id=13,name=batch 一灰灰8,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

e. 比較查詢

數字的比較查詢,比如大於等於,大於,小於,小於等於,between,下面的三個方法宣告,應該能直觀表示這種方式可以如何寫

/**
 * 查詢大於or等於指定id的所有記錄
 *
 * @param id
 * @return
 */
List<MoneyPO> findByIdGreaterThanEqual(Integer id);

/**
 * 查詢小於or等於指定id的所有記錄
 *
 * @param id
 * @return
 */
List<MoneyPO> findByIdLessThanEqual(Integer id);

/**
 * between查詢
 *
 * @param low
 * @param high
 * @return
 */
List<MoneyPO> findByIdIsBetween(Integer low,Integer high);
複製程式碼

下面是簡單的對映關係

  • > : xxGreaterThan
  • >=: xxGreaterThanEqual
  • <: xxLessThan
  • <=: xxLessThanEqual
  • !=: xxNot
  • between a and b : xxIsBetween

測試case如下

private void queryByCompare() {
    Integer id1 = 3;
    Iterable<MoneyPO> res = moneyCurdRepository.findByIdLessThanEqual(id1);
    System.out.println("findByIdLessThan 3 return: " + res);


    Integer id2 = 10;
    res = moneyCurdRepository.findByIdGreaterThanEqual(id2);
    System.out.println("findByIdGreaterThan 10 return: " + res);

    id1 = 4;
    id2 = 6;
    res = moneyCurdRepository.findByIdIsBetween(id1,id2);
    System.out.println("findByIdsWBetween 3,10 return: " + res);
}
複製程式碼

輸出結果為

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id<=?
findByIdLessThan 3 return: [MoneyPO(id=1,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=?
findByIdGreaterThan 10 return: [MoneyPO(id=10,name=batch 一灰灰5,money=1498,updateAt=2019-04-18 17:01:58.0),MoneyPO(id=11,name=batch 一灰灰6,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id between ? and ?
findByIdsWBetween 3,10 return: [MoneyPO(id=4,MoneyPO(id=6,name=Batch 一灰灰blog,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

f. 排序

排序也屬於基本查詢的case了,jpa的實現中,通過加上OrderByXxxAsc/Desc的方式來決定根據什麼進行升序or降序

/**
 * 根據money查詢,並將最終的結果根據id進行倒排
 *
 * @param money
 * @return
 */
List<MoneyPO> findByMoneyOrderByIdDesc(Long money);

/**
 * 根據多個條件進行排序
 *
 * @param id
 * @return
 */
List<MoneyPO> queryByIdGreaterThanEqualOrderByMoneyDescIdAsc(Integer id);
複製程式碼

在根據多個列進行排序時,需要注意的是不能寫多個 OrderBy 而是直接在OrderBy後面加上對應的xxxAscyyyDesc

測試程式碼如

private void queryWithSort() {
    // 排序
    Long money = 400L;
    Iterable<MoneyPO> res = moneyCurdRepository.findByMoneyOrderByIdDesc(money);
    System.out.println("findByMoneyAndOrderByIdDesc return: " + res);
  
    Integer startId = 7;
    res = moneyCurdRepository.queryByIdGreaterThanEqualOrderByMoneyDescIdAsc(startId);
    System.out.println("queryByIdGreaterThanEqualOrderByMoneyDescIdAsc return: " + res);
}
複製程式碼

輸出結果如下

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.money=? order by moneypo0_.id desc
findByMoneyAndOrderByIdDesc return: [MoneyPO(id=13,updateAt=2019-04-18 17:01:40.0)]
------------- 人工拆分 --------
Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=? order by moneypo0_.money desc,moneypo0_.id asc
queryByIdGreaterThanEqualOrderByMoneyDescIdAsc return: [MoneyPO(id=10,MoneyPO(id=8,name=Batch 一灰灰 3,MoneyPO(id=9,name=Batch 一灰灰 4,MoneyPO(id=7,name=Batch 一灰灰blog 2,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

g. 分頁查詢

分頁有兩種方式,一個是查詢最大的多少條資料,一個是正常的limit/offset方式,下面是一個簡單的例項demo

/**
* 分頁查詢,獲取前面三個資料
*
* @param id
* @return
*/
List<MoneyPO> findTop3ByIdGreaterThan(Integer id);

/**
* 分頁查詢
*
* @param id
* @param pageable page 從0開始表示查詢第0頁,即返回size個正好>id數量的資料
* @return
*/
List<MoneyPO> findByIdGreaterThan(Integer id,Pageable pageable);
複製程式碼

對於分頁而言,通過傳入引數Pageable來表明即可

測試case如

private void queryWithPageSize() {
    // 分頁查詢
    Iterable<MoneyPO> res = moneyCurdRepository.findTop3ByIdGreaterThan(3);
    System.out.println("findTop3ByIdGreaterThan 3 return: " + res);

    // id>3,第2頁,每頁3條,如果id遞增時,則返回的第一條id=4 + 2 * 3 = 10
    res = moneyCurdRepository.findByIdGreaterThan(3,PageRequest.of(2,3));
    System.out.println("findByIdGreaterThan 3 pageIndex 2 size 3 return: " + res);
}
複製程式碼

輸出結果為

Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>? limit ?
findTop3ByIdGreaterThan 3 return: [MoneyPO(id=4,updateAt=2019-04-18 17:01:40.0)]
---------- 人工拆分 ------------
Hibernate: select moneypo0_.id as id1_0_,moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>? limit ?,?
findByIdGreaterThan 3 pageIndex 2 size 3 return: [MoneyPO(id=10,updateAt=2019-04-18 17:01:40.0)]
複製程式碼

4. 小結

主要介紹了jpa的查詢的最基本使用方式,主要是根據規則定義方法名的方式來實現sql的效果,下表示一個簡單的對比小結

方法名 說明 等效sql
findByXxx 表示根據列Xxx等於傳參構建sql where xxx= ?
findByXxxAndYyy 根據多個列進行查詢 where xxx=? and yyy=?
findByXxxOrYyy 根據多個列實現or查詢 where xxx=? or yyy=?
findByXxxLike like查詢,需要注意查詢條件中加% where xxx like
findByXxxIn in查詢 where Xxx in ()
findByXxxGreaterThan 大於 where xxx > ?
findByXxxGreaterThanEqual 大於等於 where xxx >= ?
findByXxxLessThan 小於 where xxx < ?
findByXxxLessThanEqual 小於等於 where xxx <= ?
findByXxxNot 不等於 where xxx != ?
findByXxxIsBetween between查詢 where xxx between ? and ?
OrderByXxxDesc 排序 order by xxx desc
topN 分頁,表示獲取最前面的n條 limit n

此外還有一個分頁的方式是傳參Pageable,來指定具體的分頁

我們常見的查詢操作中,除了上面的一些case之外,還有一些是我們沒有提到的,如下面的一些使用姿勢,則會在後面的文章中引入

  • group by
  • distinct
  • join
  • 各種函式的支援(sum,max,min,avg...)
  • 查詢部分表中部分欄位時
  • 統計查詢

II. 其他

0. 原始碼&相關博文

原始碼

相關博文

1. 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

一灰灰blog