# 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)
> 
2. 按下鍵盤Win鍵,輸入==環境變數==,會出現==編輯系統環境變數==,點下去。
> 
3. 按照圖片順序去設定JDK。
> 
> 
> 
> 
> 
> 
> 
> 
> 
4. 按照圖片順序去設定log路徑
> 
> 
> 
> 
### 下載專案
先至github連結中下載程式碼,將該壓縮檔放到桌面,並==解壓縮至此==。
https://github.com/Weeei518/springboot-demo-backend
> 
> 
桌面會有一個專案的資料夾,如果沒改過名字應該為(springboot-demo-backend-main),將該資料夾按住拖至Intellij IDEA的捷徑。
> 
接著就會以該資料夾為根目錄用Intellij IDEA開啟。
> 如果桌面沒有捷徑的話,就先開啟Intellij IDEA,然後開啟資料夾,並選擇桌面已解壓縮後的資料夾進行開啟即可
如果有需要存取....按接受即可。
第一次開啟專案需要等待一段時間,等下方的載入條都清空再進行操作。
> 
點右上方的三角形啟動,並在下方出現Started...就代表啟動成功了
> 
### 專案環境配置(.yml)
先複製一份==application-local-example.yml== 並命名為 ==application-local.yml==

設定內容:
database的url、account、password 請輸入自己的資料庫資訊,資料庫名字使用**bookstore**

如果設定正確的話,啟動專案時不會出現任何錯誤。
下圖這種Caused by 開頭的就是代表有錯誤

### 匯入資料庫&資料表
透過以下連結下載,解壓縮後將sql資料夾中的20251211.sql匯入至mysql workbench
https://drive.google.com/file/d/1S9SL4dt6u1uoaYQM_PkwnX__1RuSRwJC/view?usp=sharing
步驟:
1. 
2. 
3. 
## 名詞介紹
### 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();
}
```