# 114後端集訓 - SpringBoot ## 簡介 1. https://drive.google.com/file/d/19LbptHLjNEhZr-2kB3gb7fM5IZ6PDAG0/view?usp=sharing 2. https://drive.google.com/file/d/1pHvS_eaSMQwuMFX2FI6tsMqxN66RAJZD/view?usp=sharing 3. https://drive.google.com/file/d/1cWDXUYgVGtfte7QBS_uVmogKLcGUKwUK/view?usp=sharing ## 環境配置 ### JDK & log路徑 JDK下載:https://drive.google.com/file/d/15l2oQpV6L8U3AHkK0HNKCiwf-GCKQBGd/view 下載後會得到一個resourcepack.7z檔,請放至桌面 > (請先自行下載7Zip) 1. 點選7-Zip → 解壓縮至此。會獲得兩個資料夾(jdk-11.0.8、log) > ![螢幕擷取畫面 2025-12-07 171008](https://hackmd.io/_uploads/BkEsBafzWl.png) 2. 按下鍵盤Win鍵,輸入==環境變數==,會出現==編輯系統環境變數==,點下去。 > ![螢幕擷取畫面 2025-12-07 171432](https://hackmd.io/_uploads/H1dnB6fMWg.png) 3. 按照圖片順序去設定JDK。 > ![螢幕擷取畫面 2025-12-07 171617](https://hackmd.io/_uploads/Hy6N8TGfbe.png) > ![螢幕擷取畫面 2025-12-07 171634](https://hackmd.io/_uploads/Sk7HUTMMbg.png) > ![螢幕擷取畫面 2025-12-07 171838](https://hackmd.io/_uploads/SJS68pzfWl.png) > ![螢幕擷取畫面 2025-12-07 171911](https://hackmd.io/_uploads/r1BpLpMzZg.png) > ![螢幕擷取畫面 2025-12-07 171932](https://hackmd.io/_uploads/ryZxP6zG-x.png) > ![螢幕擷取畫面 2025-12-07 172149](https://hackmd.io/_uploads/rJjsvTfG-x.png) > ![螢幕擷取畫面 2025-12-07 172215](https://hackmd.io/_uploads/rJ5ls6zzbg.png) > ![螢幕擷取畫面 2025-12-07 172245](https://hackmd.io/_uploads/rJ9gspMM-x.png) > ![螢幕擷取畫面 2025-12-07 172318](https://hackmd.io/_uploads/r1sl_TMG-l.png) 4. 按照圖片順序去設定log路徑 > ![螢幕擷取畫面 2025-12-07 172534](https://hackmd.io/_uploads/H1qP_6MGWx.png) > ![螢幕擷取畫面 2025-12-07 172551](https://hackmd.io/_uploads/H1cD_TfMWg.png) > ![螢幕擷取畫面 2025-12-07 172616](https://hackmd.io/_uploads/HJqvdazzbx.png) > ![螢幕擷取畫面 2025-12-07 172629](https://hackmd.io/_uploads/Bycvu6MGZx.png) ### 下載專案 先至github連結中下載程式碼,將該壓縮檔放到桌面,並==解壓縮至此==。 https://github.com/Weeei518/springboot-demo-backend > ![螢幕擷取畫面 2025-12-07 172955](https://hackmd.io/_uploads/ryDVFTGMWl.png) > ![螢幕擷取畫面 2025-12-07 173147](https://hackmd.io/_uploads/r1Oot6zz-x.png) 桌面會有一個專案的資料夾,如果沒改過名字應該為(springboot-demo-backend-main),將該資料夾按住拖至Intellij IDEA的捷徑。 > ![螢幕擷取畫面 2025-12-07 173847](https://hackmd.io/_uploads/S1-FiTMGZl.png) 接著就會以該資料夾為根目錄用Intellij IDEA開啟。 > 如果桌面沒有捷徑的話,就先開啟Intellij IDEA,然後開啟資料夾,並選擇桌面已解壓縮後的資料夾進行開啟即可 如果有需要存取....按接受即可。 第一次開啟專案需要等待一段時間,等下方的載入條都清空再進行操作。 > ![螢幕擷取畫面 2025-12-07 174607](https://hackmd.io/_uploads/BypxpTGMWe.png) 點右上方的三角形啟動,並在下方出現Started...就代表啟動成功了 > ![螢幕擷取畫面 2025-12-07 174745](https://hackmd.io/_uploads/S1Ad66MG-g.png) ### 專案環境配置(.yml) 先複製一份==application-local-example.yml== 並命名為 ==application-local.yml== ![螢幕擷取畫面 2025-12-07 174939](https://hackmd.io/_uploads/Hy38CpzfZl.png) 設定內容: database的url、account、password 請輸入自己的資料庫資訊,資料庫名字使用**bookstore** ![螢幕擷取畫面 2025-12-07 175133](https://hackmd.io/_uploads/Symme67fWg.png) 如果設定正確的話,啟動專案時不會出現任何錯誤。 下圖這種Caused by 開頭的就是代表有錯誤 ![螢幕擷取畫面 2025-12-07 175812](https://hackmd.io/_uploads/H1WCkCzGWe.png) ### 匯入資料庫&資料表 透過以下連結下載,解壓縮後將sql資料夾中的20251211.sql匯入至mysql workbench https://drive.google.com/file/d/1S9SL4dt6u1uoaYQM_PkwnX__1RuSRwJC/view?usp=sharing 步驟: 1. ![螢幕擷取畫面 2025-12-08 112943](https://hackmd.io/_uploads/ryloFL6XGZx.png) 2. ![螢幕擷取畫面 2025-12-08 113014](https://hackmd.io/_uploads/SkotUTQf-g.png) 3. ![螢幕擷取畫面 2025-12-08 113040](https://hackmd.io/_uploads/SJjK867zZg.png) ## 名詞介紹 ### Entity 定義:資料庫實體類別。 功能:代表資料庫中的一張資料表。類別中的屬性直接對應資料表的欄位。 註解:通常使用 @Entity, @Table, @Id, @Column 等 JPA 註解。 寫法參考: ```java package demo.databaseconfig.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDate; @Entity //告訴Springboot 我是Entity @Table(name = "book", schema = "bookstore") //告訴Springboot 我在哪張表 @Data //自動建立getter、setter public class Book { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "author", nullable = false, length = 10) private String author; @Column(name = "name", nullable = false) private String name; @Column(name = "info", nullable = false) private String info; @Column(name = "publish_date", nullable = false) // MYSQL TYPE 為DATE,這邊使用LocalDate型別,如果是DATETIME,這邊使用LocalDateTime型別 private LocalDate publishDate; } ``` ### DAO (Mapper / Repository) 定義:資料存取物件 (Data Access Object)。在 Spring Data JPA 中通常稱為 Repository。 功能:負責與資料庫進行實際的溝通 (CRUD 操作)。 運作:透過繼承 JpaRepository,開發者無需撰寫 SQL 即可使用 save, findById, findAll 等方法。 寫法參考: ```java package demo.databaseconfig.dao; import demo.databaseconfig.entity.Book; import org.springframework.stereotype.Repository; @Repository public interface BookDAO extends BaseDAO<Book, Integer> { } ``` ### ORM (Object Relational Mapping) **1. 核心概念** * **定義**:物件關聯對映。這是一種程式設計技術,用於將物件導向程式語言(如 Java)中的「物件」與關聯式資料庫(如 MySQL)中的「資料表」建立對應關係。 * **橋樑角色**:ORM 就像一位「翻譯官」,它負責將 Java 物件的屬性翻譯成資料庫的欄位,並將 Java 的操作方法(如 `save`, `delete`)翻譯成 SQL 語法(如 `INSERT`, `DELETE`)。 * **解決問題**:解決了「物件導向模型」與「關聯式資料庫模型」之間的不匹配問題(Impedance Mismatch)。 **2. 運作方式** * **O (Object)**:Java 中的 Class(類別),例如 `BookEntity`。 * **R (Relational)**:資料庫中的 Table(資料表),例如 `book` 表。 * **M (Mapping)**:透過設定檔或 Java 註解(如 `@Entity`, `@Column`, `@Table`)來定義兩者如何對應。 **3. ORM 與傳統 JDBC 的比較** | 特性 | 傳統 JDBC | ORM (如 Hibernate/JPA) | | :--- | :--- | :--- | | **SQL 撰寫** | 需要手動撰寫大量 SQL 字串。 | 自動生成 SQL,開發者操作物件即可。 | | **程式碼量** | 繁瑣,需處理連線、例外。 | 簡潔,專注於商業邏輯。 | | **維護性** | 修改資料表欄位時,需手動修改多處 SQL 字串。 | 修改 Entity 類別屬性即可,對應關係自動更新。 | **4. Spring Boot 中的 ORM** * **JPA (Java Persistence API)**:Java 官方訂定的標準介面(規格書),定義了如何進行 ORM,但本身不實作功能。 * **Hibernate**:JPA 最主流的實作框架。Spring Boot 預設使用 Hibernate 來完成 ORM 的實際工作。 * **Spring Data JPA**:Spring 封裝了 JPA/Hibernate,提供更簡易的 Repository 介面,讓開發者甚至不需要寫實作類別。 ### Bean (DTO) 定義:一般的 Java 物件 (POJO),此處特指 Data Transfer Object。 功能:負責在不同層級 (如 Controller 與 Service 之間) 傳遞數據。 特點:不含任何商業邏輯,僅包含屬性 (Fields) 與 Getter/Setter,可做欄位驗證。 ### Service 定義:商業邏輯層。 功能:系統的核心大腦。負責接收 Controller 的請求,執行運算、驗證邏輯,並呼叫 DAO 存取資料。 事務控制:通常在此層級使用 @Transactional 管理資料庫事務。 ### Transformer 定義:物件轉換器。 功能:負責 Entity 與 Bean (DTO) 之間的相互轉換。 Entity 轉 Bean:從資料庫取出資料後,轉為 DTO 傳給前端 (過濾敏感資訊)。 Bean 轉 Entity:接收前端傳來的 DTO,轉為 Entity 準備存入資料庫。 好處:將轉換邏輯從 Controller 或 Service 中抽離,保持程式碼整潔。 ### Controller 定義:控制器 (Web 層)。 功能:接收 HTTP 請求 (GET, POST, PATCH, DELETE等),解析參數,呼叫 Service 執行邏輯,並回傳結果。 職責:只負責「分發」任務,不應包含複雜的商業邏輯。 ### Json 定義:JavaScript Object Notation。 功能:一種輕量級的資料交換格式。 與 Controller 的關係: 當前端發送請求時,Controller 將 JSON 字串自動解析為 Java Bean (@RequestBody)。 格式範例: 物件: 由一組鍵值對組成,用花括號 {} 括起來。 陣列: 由一組有序的值組成,用方括號 [] 括起來。 ``` { "name": "John Doe", "age": 30, "isStudent": false, "courses": ["Math", "Science"], "address": { "city": "New York", "zip": "10001" } } ``` --- ## 查詢 API ### 使用 `ObjectData` 和 `ArrayData` 手動構建 JSON #### 1. `ObjectData` 和 `ArrayData` 概念 * **`ObjectData`**:用來表示一個物件,包含多個鍵值對(例如,書籍資料中的書名、出版日期等)。 * **`ArrayData`**:用來表示一個資料陣列,可以存放多個 `ObjectData` 物件(例如,一個書籍列表)。 這兩個類型主要用來幫助我們結構化資料並最終轉換為 JSON 格式。 --- #### 2. 在 Spring Boot 中手動構建 JSON 資料 ##### 2.1 定義 `ObjectData` 和 `ArrayData` 假設我們需要手動構建一個包含書籍資料的 JSON 回應,我們會使用 `ObjectData` 來表示書籍的每一項資訊,然後將多本書放入 `ArrayData` 中。 ```java // 假設有 ObjectData 類用來儲存單一書籍資料 ObjectData book1 = new ObjectData(); book1.add("name", "Accessxxxxxxx"); book1.add("publicationDate", "2025/01/01"); book1.add("author", "me"); // 創建 ArrayData 用來儲存書籍陣列 ArrayData books = new ArrayData(); books.add(book1); ``` ##### 2.2 返回 JSON 格式的資料 在 Spring Boot 中,我們可以將 `ObjectData` 和 `ArrayData` 組成的資料作為 JSON 回應返回。這裡使用 `ResponseEntity` 來回傳資料。 ```java import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BookController { @GetMapping("/books") public ResponseEntity<String> getBooks() { // 建立書籍資料 ObjectData book1 = new ObjectData(); book1.add("name", "Accessxxxxxxx"); book1.add("publicationDate", "2025/01/01"); book1.add("author", "me"); // 創建 ArrayData 並將書籍加入其中 ArrayData books = new ArrayData(); books.add(book1); // 回傳 JSON 格式的資料 return ResponseEntityBuilder.success() .message("查詢成功") .data(books) .build(); } } ``` 當你發送請求到 `/books` 時,Spring Boot 會自動將 `ArrayData` 轉換成 JSON 格式,並返回類似以下結構的資料: ```json { "result":true, "errorCode":"", "message":"查詢成功", "data":[ { "name":"Accessxxxxxxx", "publicationDate":"2025/01/01", "author":"me" } ] } ``` --- ##### 3. 小結 * **`ObjectData`** 用來表示單個物件的資料(如書籍的名稱、日期、作者等)。 * **`ArrayData`** 用來儲存多個 `ObjectData` 物件(如多本書的資料列表)。 * 在 Spring Boot 中,我們可以手動組織資料,將其作為 JSON 格式回應返回給客戶端。 這樣就可以靈活地組織和傳遞 JSON 資料了! --- ## API接收參數的4種方法 ### 1. **接收 `form-data` 參數** `form-data` 參數通常來自HTML表單的`POST`請求。當使用`application/x-www-form-urlencoded`或`multipart/form-data`格式的表單提交數據時,不須特別添加註解,即為form-data接收。 #### 範例:接收 `form-data` 參數 ```java @PostMapping("/submitForm") public String submitForm(String name, int age) { return "Name: " + name + ", Age: " + age; } ``` --- ### 2. **接收 `JSON` 參數** 當API接收到的請求內容是`JSON`格式時,通常使用`@RequestBody`來將JSON數據轉換成對應的Java物件。這在接收複雜的資料結構(例如,物件、列表等)時非常有用。 #### 範例:接收 `JSON` 參數 ```java @PostMapping("/addBook") public String addBook(@RequestBody Book book) { // 假設Book是一個Java類,具有name, author等屬性 return "Book added: " + book.getName(); } ``` 傳入的json為: ```json { "name":"書名A" } ``` --- ### 3. **接收 `PathVariable` 參數** `PathVariable` 用來接收URL路徑中的動態參數。當URL中的某部分是變動的,可以通過`@PathVariable`來將其提取並映射到方法參數中。 #### 範例:接收 `PathVariable` 參數 ```java @GetMapping("/books/{id}") public String getBookById(@PathVariable int id) { return "Fetching book with ID: " + id; } ``` #### 使用情境: * * 當URL包含查詢參數,例如`/books/1`。 --- ### 4. **接收 `RequestParam` 參數** `RequestParam` 用來接收URL查詢參數。在GET請求中,查詢參數(例如`?key=value`)通常是我們通過`@RequestParam`來獲取的。 #### 範例:接收 `RequestParam` 參數 ```java @GetMapping("/search") public String searchBooks(@RequestParam String query) { return "Searching for books with query: " + query; } ``` #### 使用情境: * 當URL包含查詢參數,例如`/search?query=springboot`。 --- ### 小結: | 方式 | 用途 | 範例 | | --------------- | -------------------------- | ---------------------------- | | `不打` | 用來接收表單數據 | `String query` | | `@RequestBody` | 用來接收`JSON`格式的請求體數據 | `@RequestBody Book book` | | `@PathVariable` | 用來接收URL中的動態部分(例如ID) | `@PathVariable int id` | | `@RequestParam` | 用來接收URL中的查詢參數(`key=value`) | `@RequestParam String query` | ## 表單驗證 | 註解類型 | 描述 | 例子 | | ----------- | ---------------- | -------------------------------------------------------------------------------- | | `@Null` | 檢查字段是否為`null` ,不為null則報錯 | `@NotNull(message = "Name cannot be null")` | | `@NotNull` | 檢查字段是否為`null` ,為null則報錯 | `@NotNull(message = "Name cannot be null")` | | `@NotBlank` | 檢查字段是否為空或只包含空白字符,為blank則報錯 | `@NotBlank(message = "Name cannot be blank")` | | `@Size` | 檢查字符串大小是否在指定範圍內,不在範圍內報錯 | `@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")` | | `@Min` | 檢查數字是否大於或等於指定值 ,低於指定值報錯 | `@Min(value = 18, message = "Age must be at least 18")` | | `@Max` | 檢查數字是否小於或等於指定值 ,高於指定值報錯 | `@Max(value = 100, message = "Age must be less than 100")` | BookTransformer (interface): ```java package demo.service.transformer; import demo.bean.BookBean; import demo.databaseconfig.entity.Book; public interface BookTransformer extends BeanEntityTransformer<BookBean, Book> { } ``` BookTransformerImpl (class): ```java package demo.service.transformer.impl; import demo.bean.BookBean; import demo.databaseconfig.entity.Book; import demo.service.transformer.BookTransformer; import org.springframework.stereotype.Component; import tw.edu.ntub.birc.common.util.JavaBeanUtils; import javax.annotation.Nonnull; @Component public class BookTransformerImpl implements BookTransformer { @Nonnull @Override public Book transferToEntity(@Nonnull BookBean bookBean) { return JavaBeanUtils.copy(bookBean, new Book()); } @Nonnull @Override public BookBean transferToBean(@Nonnull Book book) { return JavaBeanUtils.copy(book, new BookBean()); } } ``` BookService (interface): ```java package demo.service; import demo.bean.BookBean; public interface BookService extends BaseService<BookBean,Integer> { } ``` BookServiceImpl (class): ```java package demo.service.impl; import demo.bean.BookBean; import demo.databaseconfig.dao.BookDAO; import demo.databaseconfig.entity.Book; import demo.service.BookService; import demo.service.transformer.BookTransformer; import org.springframework.stereotype.Service; @Service public class BookServiceImpl extends BaseServiceImpl<BookBean, Book, Integer> implements BookService { private final BookDAO bookDAO; private final BookTransformer bookTransformer; public BookServiceImpl(BookDAO bookDAO, BookTransformer bookTransformer) { super(bookDAO, bookTransformer); this.bookDAO = bookDAO; this.bookTransformer = bookTransformer; } @Override public BookBean save(BookBean bookBean) { Book book = bookTransformer.transferToEntity(bookBean); book = bookDAO.save(book); return bookTransformer.transferToBean(book); } } ``` ```java @GetMapping("/book") public ResponseEntity<String> searchAll() { List<BookBean> bookBeanList = bookService.searchAll(); ArrayData arrayData = new ArrayData(); for (BookBean bookBean : bookBeanList) { ObjectData objectData = arrayData.addObject(); objectData.add("id", bookBean.getId()); objectData.add("name", bookBean.getName()); objectData.add("publicationDate", bookBean.getPublishDate()); objectData.add("authorName", bookBean.getAuthor()); } return ResponseEntityBuilder.success() .message("查詢成功") .data(arrayData) .build(); } @GetMapping(path = "/book/{id}") public ResponseEntity<String> getBook(@PathVariable Integer id) { Optional<BookBean> bookBeanOptional = bookService.getById(id); BookBean bookBean = bookBeanOptional.orElseThrow(() -> new NotFoundException("找不到" + id)); ObjectData objectData = new ObjectData(); objectData.add("id", bookBean.getId()); objectData.add("name", bookBean.getName()); objectData.add("publicationDate", bookBean.getPublishDate()); objectData.add("authorName", bookBean.getAuthor()); return ResponseEntityBuilder.success() .message("查詢成功") .data(objectData) .build(); } ```