本章是全書的核心之一:完整實作書籍的 CRUD API, 正確運用 、、、、 五種 HTTP 方法,並建立統一的錯誤處理機制。
REST 核心概念
REST(REpresentational State Transfer)是一種 API 設計風格,核心思想:
一切都是資源(Resource):書籍、使用者……每種資源用一個 URI 表示。
用 HTTP 方法表達動作:URI 只寫「名詞」,動作交給 HTTP 方法。 不要設計 或 這種把動詞放進 URI 的風格。
無狀態(Stateless):伺服器不記住客戶端的狀態, 每個請求都攜帶完整資訊(第 5 章的 JWT 正是為此而生)。
URI 設計慣例:
lP6.4cm 慣例 & 範例
用「複數名詞」表示資源集合 & (而非 )
用路徑區隔單一資源 &
查詢、分頁、排序用 Query String &
不要把動詞放進 URI & (而非 )
五種 HTTP 方法完整解析
llP3.4cmlll 方法 & 語意 & 本專案範例 & 成功狀態碼 & 冪等 & 安全
GET & 讀取資源 & 查詢書籍列表/單筆 & 200 & &
POST & 建立資源 & 新增一本書 & 201 & \(\times\) & \(\times\)
PUT & 完整替換資源 & 更新書籍全部欄位 & 200 & & \(\times\)
PATCH & 部分修改資源 & 只調整書籍價格 & 200 & \(\times\)1& \(\times\)
DELETE & 刪除資源 & 刪除一本書 & 204 & & \(\times\)
安全=不改變伺服器上的資料(只有讀取類的 GET 是安全的)。
冪等=同一請求執行 1 次和執行 N 次,資源的最終狀態相同:
(整包覆寫)做十次,結果都一樣 \(\to\) 冪等。
(每次建立新資料)做十次會有十筆 \(\to\) 非冪等。
做十次,資源狀態都是「不存在」\(\to\) 冪等 (第二次起雖回 404,資源狀態並沒有改變)。
冪等性很重要:網路逾時重送請求時,冪等的操作可以放心重試。
PUT 與 PATCH 的差異(高頻考點)
P5.5cmP5.5cm PUT——完整替換 & PATCH——部分更新
必須提供資源的「所有」欄位 & 只提供「想修改」的欄位
沒帶到的欄位視同要被清空/重設 & 沒帶到的欄位維持原值
本專案用 (欄位全部必填) & 本專案用 (欄位全部可省略)
例如只想把價格改成 499:
// PATCH /api/books/1 —— 只帶要改的欄位,其他欄位不動
{ "price": 499.00 }
// 若用 PUT,就必須把 title、author、isbn、stock... 全部帶上,
// 漏帶任何一個必填欄位都會收到 400 驗證錯誤HTTP 狀態碼
llP7.2cm 狀態碼 & 名稱 & 本專案使用時機
& OK & 查詢/更新成功
201 & Created & 新增成功(附 Header 指向新資源)
204 & No Content & 刪除成功(沒有回應本文)
400 & Bad Request & 輸入驗證失敗、JSON 格式錯誤
401 & Unauthorized & 未登入、token 無效或過期(認證失敗)
403 & Forbidden & 已登入但角色權限不足(授權失敗)
404 & Not Found & 查無資源(例如書籍 id 不存在)
409 & Conflict & 資料衝突(ISBN/帳號/Email 重複)
500 & Internal Server Error & 伺服器未預期錯誤
本專案 API 總覽
llP4.2cml 方法 & 路徑 & 說明 & 權限
POST & & 註冊新使用者 & 公開
POST & & 登入並取得 JWT & 公開
GET & & 分頁查詢所有書籍 & 公開
GET & & 查詢單一書籍 & 公開
GET & & 關鍵字搜尋 & 公開
POST & & 新增書籍 & USER / ADMIN
PUT & & 完整更新書籍 & USER / ADMIN
PATCH & & 部分更新書籍 & USER / ADMIN
DELETE & & 刪除書籍 & 僅 ADMIN
DTO 與輸入驗證
安全:若直接用 Entity 接收 JSON,客戶端可以偷塞 、 等不該由外部控制的欄位 (Mass Assignment 漏洞)。
解耦:API 欄位與資料表結構可以各自演進,互不牽制。
驗證:驗證規則集中寫在 DTO,Entity 保持乾淨。
常用驗證註解:
lP7.6cm 註解 & 規則
& 不可為 null(任何型別)
& 字串不可為 null/空字串/全空白
& 字串長度或集合大小範圍
/ & 數值上下限
& 小數下限(適用 BigDecimal)
& Email 格式
& 自訂正規表達式
驗證註解要生效,Controller 的參數前必須加 。 另外注意:、 等註解遇到 null 會「跳過不驗」, null 的把關是 / 的責任—— 這個特性正好被 用來實現「只驗證有傳的欄位」。
Service:商業邏輯層
幾個設計重點:
建構子注入:依賴宣告為 ,由建構子傳入。 比 欄位注入更明確、更好測試,是官方推薦作法。
查無資料就拋例外: 統一處理查無資料的情況, 直接拋出 ,由全域例外處理器轉成 404。
PATCH 的實作:逐欄位判斷「不是 null 才更新」, 完美對應 PATCH 的部分更新語意。
商業規則:ISBN 唯一性檢查放在 Service(而不是 Controller), 因為這是「商業邏輯」,無論請求從哪裡來都必須遵守。
Controller:控制層
三種「取得請求資料」的註解:
lP4.8cmP4.6cm 註解 & 資料來源 & 範例
& URI 路徑的一部分 & 中的
& Query String &
& 請求本文(JSON) & POST/PUT/PATCH 的書籍資料
讓你完整控制 HTTP 回應的三要素: 狀態碼( = 200、 = 201、 = 204)、Header(如 )、 本文(自動序列化成 JSON 的物件)。
統一例外處理
沒有統一處理時,錯誤會以 Spring 預設格式(甚至 HTML)回傳,格式混亂。 我們的做法:自訂例外 + 統一錯誤格式 + 全域攔截。
例外與狀態碼的對應關係:
lP5cml 例外 & 觸發情境 & 狀態碼
& 查無資料 & 404
& ISBN/帳號重複 & 409
& 驗證失敗 & 400
& JSON 格式錯誤 & 400
& 帳密錯誤 & 401
& 角色權限不足 & 403
其他 & 未預期錯誤 & 500
驗證失敗(400)的實際回應範例—— 逐欄說明錯在哪:
{
"timestamp": "2026-08-14T10:30:00",
"status": 400,
"error": "Bad Request",
"message": "輸入資料驗證失敗",
"path": "/api/books",
"fieldErrors": {
"title": "書名不可為空白",
"price": "價格不可為負數"
}
}用 curl 快速測試
啟動專案後,先測試不需要登入的公開查詢 API:
# 查詢所有書籍(分頁)
curl "http://localhost:8080/api/books?page=0&size=10"
# 查詢單一書籍
curl http://localhost:8080/api/books/1
# 關鍵字搜尋
curl "http://localhost:8080/api/books/search?keyword=Java"
# 查詢不存在的書籍 -> 觀察 404 統一錯誤格式
curl -i http://localhost:8080/api/books/99999
# -i 參數會一併顯示回應的狀態列與 Header寫入類的 API(POST/PUT/PATCH/DELETE)需要 JWT, 下一章完成認證機制後就能完整測試。
PATCH 規範上「不保證」冪等;若實作成「設定欄位值」(如本專案),實際行為是冪等的。↩︎