在 Day23 內容裡,我們學會了如何設定 H2 和 PostgreSQL 資料庫連線。現在要來學習最重要的一步:定義一個 Entity (實體)。
Entity 是一個特殊的 Java 物件,它會被 JPA「對映 (Mapping)」到資料庫中的一張資料表。學會定義 Entity,就等於學會了如何設計你的資料庫結構。今天將從頭開始,一步步建立一個功能完整的 Customer
(客戶) Entity。
完成今日學習後,你將能夠:
@Entity
、@Table
和 @Column
來建立你的資料模型。@Column
的屬性及 @Size
等 Annotation (註解) 來確保資料的完整性與正確性。@GeneratedValue
搭配 UUID
,這是現代化應用程式中非常推薦的主鍵 (Primary Key) 策略。@Entity
& @Table
)首先,我們需要一個普通的 Java 類別 (POJO),然後透過加上 Annotation 把它變成 JPA 管理的 Entity。
@Entity
:這是最重要的 Annotation,它告訴 JPA:「嘿!這個類別不是普通的類別,請幫我管理它,並將它對應到資料庫的一張資料表。」@Table(name = "customers")
:這個 Annotation 是選用的,但強烈建議使用。它能讓我們明確指定這個 Entity 對應的資料表名稱。如果不寫,JPA 預設會使用類別名稱 (例如 Customer
會對應到 customer
表)。明確指定 customers
(複數) 是業界常見的命名慣例。import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "customers")
public class Customer {
// ... 接下來我們會在這裡加入欄位
}
@Id
& @GeneratedValue
)每一張資料表都需要一個主鍵 (Primary key) 來唯一識別每一筆紀錄。傳統上我們常用自動遞增的數字 (Auto-increment Integer),但在分散式系統或需要更高安全性的現代應用中,UUID (Universally Unique Identifier) 是更佳的選擇。
@Id
:用來標示哪個欄位是主鍵 (Primary key)。@GeneratedValue(strategy = GenerationType.UUID)
:這行是關鍵!它告訴 JPA,當我們新增一筆資料時,請自動生成一個 UUID 作為主鍵 (Primary key) 的值。這是 JPA 2.1 / 3.1 之後的標準作法,Hibernate 會聰明地將它對應到 PostgreSQL 原生的 uuid
資料類型,效能非常好。import java.util.UUID;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
// ... 其他欄位
}
@Column
& @Size
)現在我們來定義 name
和 email
兩個欄位,並加上一些規則。
區分「資料庫約束」與「應用程式驗證」
這是一個非常重要的觀念:
@Column
Annotation 設定,它會影響 DDL (Data Definition Language) 指令,直接在資料庫層級建立規則。例如,VARCHAR(50)
、UNIQUE
。@Size
、@NotNull
等 Bean Validation Annotation 設定。它在 Java 應用程式層級運作,在執行 SQL 語句之前就檢查資料是否合法。這能提供更即時、更友好的錯誤回饋。最佳實務是兩者都用! 這樣可以做到雙重保險。
name
欄位:我們希望它在資料庫中是 VARCHAR(50)
,並且在應用程式中驗證長度介於 2 到 50 之間。email
欄位:我們希望它在資料庫中是唯一的 (UNIQUE
約束),避免重複註冊。// ... import ...
import jakarta.persistence.Column;
import jakarta.validation.constraints.Size;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
// @Column 定義了資料庫層級的屬性
// length=50 會在 DDL 中產生 VARCHAR(50)
// nullable=false 會產生 NOT NULL 約束
@Column(name = "name", length = 50, nullable = false)
// @Size 則是在應用程式層級進行驗證
@Size(min = 2, max = 50)
private String name;
// unique=true 會在資料庫中為此欄位建立一個 UNIQUE 約束
@Column(name = "email", unique = true, nullable = false)
private String email;
// ... 其他欄位
}
註:
@Size
註解來自 Jakarta Bean Validation API。因此需要在 pom.xml 中加入 Bean Validation 的相依套件。<!-- Bean Validation 驗證相依性,提供 @Size, @NotNull 等驗證註解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
加入這個相依套件後,您就可以使用各種驗證註解,如:
@Size
- 驗證字串長度或集合大小@NotNull
- 驗證欄位不為 null@NotEmpty
- 驗證欄位不為空@Pattern
- 驗證正則表達式加入這個相依套件後,您就> 可以使用各種驗證註解,如:@Size
- 驗證字串長度或集合大小@NotNull
- 驗證欄位不為 null@NotEmpty
- 驗證欄位不為空@Pattern
- 驗證正則表達式
@Enumerated
)在應用程式中,我們常用 ENUM 來表示一組固定的狀態,例如 ACTIVE
(啟用) 和 INACTIVE
(停用)。JPA 提供了 @Enumerated
讓我們可以方便地將 ENUM 存入資料庫。
@Enumerated
有兩種儲存策略 (Strategy):
EnumType.ORDINAL
:儲存 ENUM 的順序 (從 0 開始的整數)。這是一個危險的選項! 如果未來你修改了 ENUM 的順序 (例如在中間插入一個新的狀態),所有舊資料的意義都會錯亂。強烈不建議使用。首先,定義我們的狀態 ENUM:
CustomerStatus.java
public enum CustomerStatus {
ACTIVE,
INACTIVE
}
然後,在 Customer
Entity 中使用它:
// ... import ...
import jakarta.persistence.Enumerated;
import jakarta.persistence.EnumType;
@Entity
@Table(name = "customers")
public class Customer {
// ... id, name, email 欄位 ...
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private CustomerStatus status;
// ... Getters and Setters ...
}
完整範例:Customer
Entity
好了,讓我們把所有部分組合起來,看看完整的 Customer.java
和 CustomerStatus.java
檔案。
CustomerStatus.java
public enum CustomerStatus {
ACTIVE,
INACTIVE
}
Customer.java
import com.example.demo.enums.CustomerStatus;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import java.util.UUID;
/**
* Customer Entity 代表 'customers' 資料表.
*/
@Entity
@Table(name = "customers")
public class Customer {
/**
* 主鍵 (Primary Key).
* 使用 UUID 類型,並由 JPA 自動生成。
* 對應到 PostgreSQL 的 'uuid' 類型。
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
/**
* 客戶名稱.
* 資料庫層級:VARCHAR(50), NOT NULL
* 應用程式層級:長度必須在 2 到 50 之間
*/
@Column(name = "name", length = 50, nullable = false)
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
/**
* 客戶 Email.
* 資料庫層級:UNIQUE, NOT NULL
* uniqueness 會在資料庫層級強制執行。
*/
@Column(name = "email", unique = true, nullable = false)
private String email;
/**
* 客戶狀態.
* 使用 ENUM 類型,並在資料庫中儲存為字串 ("ACTIVE", "INACTIVE")。
*/
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private CustomerStatus status;
// --- Constructors, Getters, and Setters ---
public Customer() {
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public CustomerStatus getStatus() {
return status;
}
public void setStatus(CustomerStatus status) {
this.status = status;
}
}
已經成功建立了一個結構良好、包含多種實用約束的 JPA Entity。
@Entity
和 @Table
開始,定義了類別與資料表的對映。@Id
和 @GeneratedValue
實現了現代化的 UUID 主鍵策略。@Column
和 @Size
為欄位加上了資料庫層級和應用程式層級的雙重保障。@Enumerated(EnumType.STRING)
優雅地處理了狀態欄位。