https://unsplash.com/photos/LmyPLbbUWhA
為了在 Spring Boot 專案中存取 MySQL 資料庫,我們可借助 Spring Data JPA 框架所提供的 repository 介面。
本文除了透過內建的 CRUD 方法進行存取,也會設計自己的查詢條件,包含透過方法名稱及原生語法。最後說明如何排序與分頁。
本文的練習用專案:
https://github.com/ntub46010/SpringBootTutorial/tree/Ch8-2
一、實體類別介紹
讓我們快速回顧上一篇的實體類別。以下的「Student」類別描述了學生資料,會儲存到資料庫中。
以下的「BloodType」是個列舉類別,包含 4 種血型。
以下的「Contact」類別是描述聯繫方式,包含信箱與電話這 2 個欄位。
以下是上一篇提到的 AuditorAware 元件。用途是在插入或更新資料時,自動在實體類別中具有 @CreatedBy 或 @LastModifiedBy 注解的欄位,填入使用者資訊,此處以隨機字串代替。
二、認識 Spring Data 的 Repository 介面
(一)建立 Repository 介面
在第 6 課,我們有練習過建立一個「ProductRepository」介面,將其注入到商業邏輯中。還提供兩種實作類別,分別用 List 與 Map 結構來儲存範例資料。
本節會建立一個特殊的介面,它是由 Spring Data JPA 所提供,其定位與上述的 ProductRepository 相同。但我們不必親自實作它,而是交給框架處理。
這個「StudentRepository」介面繼承了 JpaRepository 介面,並分別傳入實體類別與主鍵類別做為泛型。
(二)查詢語法與物件對映
使用 Spring Data 時,我們最直接感受到的好處,就是「產生查詢語法」與「物件對映」。
在 JpaRepository 介面,以及它的父介面中,已經有內建一些基本方法。舉例來說,我們可以將 Student 物件傳入 repository 的 save 方法。
僅僅一個方法呼叫,Spring Data 就會產生類似下面的 MySQL 指令:
INSERT INTO `student` (`name`, `grade`, `blood_type`, `birthday`) VALUE ("Vincent", 4, "A", "1996-01-01");
此時該方法會回傳插入成功的資料,且包含由 MySQL 產生的 id。
又或者是呼叫 findById 方法,則會產生如下的指令:
SELECT * FROM `student` WHERE `id` = 1;
此時該方法除了回傳符合條件的結果,更重要的是資料會被轉換成 Java 物件,也就是 Student。資料庫中的資料,與程式物件互相轉換,這項技術在關聯式資料庫稱為「物件關聯對映」(Object-Relational Mapping,ORM)。
從上面這兩個例子,我們可看出 Spring Data 會解讀 repository 的方法名稱、傳入參數,以及回傳值型態,產生對應的資料庫語法。
這段過程是透過底層的 ORM 框架來完成,Spring Data JPA 預設是採用「Hibernate」。
三、使用 JPA Repository 進行 CRUD
本節讓我們來實際使用 JPA Repository 提供的內建方法。
為了在程式中有地方呼叫 StudentRepository,讀者可在 Controller 準備 API,屆時便能搭配如 Postman 之類的工具,對後端發送 request。
(一)插入資料
以下提供一支 API,用來建立學生資料。
呼叫 save 方法即可。若 Student 實體的主鍵值是 null,或 table 中並沒有該 id 的資料,則視為插入。若有該 id 的資料,則視為更新。
插入資料時,一律由 MySQL 自行產生 id 值,我們無法自行給定。該方法最後會回傳含有 id 的資料,此處附加到 response header 中,讓 API 回傳。
此時讀者也可順道確認一下,Spring Data JPA 是否有自動在 createdTime、updatedTime、createdBy 與 updatedBy 欄位填入值。
(二)取得資料
以下是查詢指定 id 的資料,呼叫 findById 方法即可。
以下是給予字串 List,查詢多筆指定 id 的資料。呼叫 findAllById 方法即可。
攜帶 query string 存取此 API 的方式,示意如下:
GET /students/ids?idList=111,222,333
(三)更新資料
以下是更新指定 id 的資料。
在邏輯中,我們先判斷該筆資料是否存在。是的話,就將 request 中的資料更新上去,最後呼叫 save 方法儲存。由於呼叫該方法時,table 已有該 id 的資料,因此會執行更新操作,而非插入。
(四)刪除資料
以下是刪除指定 id 的資料,呼叫 deleteById 方法即可。
其他內建方法尚有 saveAll、deleteAllById、existsById、count 等,讀者可自行探索。
四、自定義查詢方法
我們能在 repository 中,依照特定的命名規則,設計自己的查詢方法。
筆者在「【Spring Boot】第7.3課-在 MongoRepository 定義查詢條件與排序方式」文章,已經設計過各種查詢方法,以及排序的方式。雖然是 MongoDB,但 Spring Data 提供的 repository,其使用方式大部份都相同。
本節僅挑選一部份做快速的示範。更多命名方式,讀者可參考 Spring Data JPA 官方文件。
(一)相等條件
以下方法是以名字做為查詢條件。
方法名稱中,在 findBy 關鍵字後面緊接著實體類別的欄位名稱即可。
以下方法是查詢內部欄位,分別是將聯繫方式的信箱與電話當作條件。只要在方法名稱將欄位的「路徑」寫出即可。
(二)範圍條件
以下 2 個方法,分別是查詢年級大於等於,和小於等於某個值的資料。
數值欄位可使用 GreaterThanEqual、GreaterThan、LessThanEqual 與 LessThan 關鍵字。
以下 2 個方法,分別是查詢生日在某天之後,和之前的資料。
日期欄位可使用 After 與 Before 關鍵字。
(三)組合多個條件
查詢條件可透過 And 或 Or 的邏輯組合起來。
以下的條件是聯繫方式的信箱等於某個值,或電話等於另一個值。
(四)原生語法
Spring Data 也支援我們直接撰寫原生語法,而方法可隨意取名。
使用 @Query 注解,可以用字串的形式提供語法。而當中的「?1」、「?2」等符號,代表要取用方法的第幾個參數(位置從 1 開始算)。並且 nativeQuery 參數需給予 true 值,代表這是原生語法。
由於語法並不是本文的重點,有興趣的讀者,可參考筆者的「【MySQL】資料的增刪改查與條件撰寫」文章。
要注意的是,SQL 語法結尾請不要加分號 ;,避免被認為語法有誤。例如上述的「?2」代表第二個參數,但 JPA 會解讀成「?2;」,並認為我們寫錯了,拋出 Ordinal parameter label was not an integer 的例外訊息。
又或者是本文第五節的排序,假設原生語法寫成 SELECT * FROM `student`;,則 JPA 會將排序語法直接附加在後,變成:
SELECT * FROM `student`; ORDER BY `grade` ASC
此時底層的 Hibernate 在執行時,勢必會拋出語法錯誤的例外。
五、排序與分頁
(一)排序
要透過 repository 的方法進行排序,首先得知道如何建立 Sort 型態的物件。
排序的欄位與方向必須一起提供,或兩者都不提供。若有不合理的值,便呼叫 Sort.unsorted 方法,建立不排序的方式。
呼叫 Sort.Order.asc 或 Sort.Order.desc 方法,傳入欄位名稱,分別可建立遞增或遞減的排序方式,以 Order 物件表示。
將一至多個 Order 物件傳入 Sort.by 方法,可得到 Sort 物件。它代表整體的排序規則,實現多重排序也不成問題。
建立出 Sort 物件後,傳入 repository 的 findAll 方法即可。
(二)分頁
當資料量太多,實務上會在排序之後,藉由「分頁」(pagination)的做法,分批從資料庫取得資料,避免造成系統負擔。
要透過 repository 的方法進行排序與分頁,需在方法傳入 Pageable 型態的參數。
以下是建立 Pageable 物件的方式。
這個 Pageable 是一個介面,而 Spring Data 內建了叫做 PageRequest 的實作類別。
分頁的頁數與每頁筆數必須一起提供,或兩者都不提供。若有不合理的值,便呼叫 Pageable.unpaged 方法,建立不分頁的方式。
呼叫 PageRequest.of 方法,依序傳入「第幾頁」、「每頁筆數」,以及前面提到的 Sort 物件,就能建立出 PageRequest 物件。
最後將 Pageable 物件傳入 repository 的方法即可。
本文示範了使用 JPA Repository 對單一 table 進行 CRUD。接下來讓我們進入 2 張 table 的範疇,學習如何配置資料表的關聯。
本文的完成專案:
https://github.com/ntub46010/SpringBootTutorial/tree/Ch8-3
上一篇:【Spring Boot】第8.2課-使用 JPA 設計實體類別與 MySQL 資料表欄位
感謝大大的用心分享,想盡點微薄之力幫助大大完善教學
回覆刪除public ResponseEntity> getStudents(
@RequestParam(required = false) String sortField,
@RequestParam(required = false) String sortDirection,
// page、size應從參數取得
@RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer size
) {
Sort sort = createSort(sortField, sortDirection);
Pageable pageable = createPageable(page, size, sort);
List students = studentRepository.find(pageable);
return ResponseEntity.ok(students);
}
謝謝你,我漏寫了,已經補上囉
刪除