在昨天的文章,我們設計了簡單的 model 類別,讓它對應到資料庫的 table。接著測試了 CRUD 流程。而本文將會介紹設計欄位的方式,包含名稱、長度、唯一性,嵌入物件欄位等等。
筆者使用的關聯式資料庫為 MySQL,但由於我們是面向 JPA 介面去實作,與底下使用哪一款 DB 並無關係,所以不會看到跟 MySQL 相關的程式。
此篇在 2024 年於「【Spring Boot】第8.2課-使用 JPA 設計實體類別與 MySQL 資料表欄位」文章更新。
首先來看看本文要使用的 model,它是一個描述學生資料的類別。
@Entity
@Table(name = "student")
public class StudentPO {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String username;
private int age;
private BloodType bloodType;
// getter, setter ...
}
public enum BloodType {
A, B, AB, O
}
我們知道 model 類別會對應一個 table。因此管理這個類別,就相當於管理 table 欄位。
設計欄位時,會使用 @Column
標記(annotation),它提供一些用來設定的參數。
BigDecimal
型態。用來定義值的位數與小數位數,請見參考資料。接下來將 @Column
用在 model 類別上,如下。
@Entity
@Table(name = "student")
public class StudentPO {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private long id;
@Column(name = "name", nullable = false, length = 50)
private String name;
@Column(name = "username", nullable = false, length = 50, unique = true)
private String username;
@Column(name = "age")
private int age;
@Column(name = "blood_type")
@Enumerated(EnumType.STRING)
private BloodType bloodType;
// ...
}
另外,如果欄位是一個 Enum,可以冠上 @Enumerated
標記,將欄位值存為 Enum 值的名字。否則預設會儲存成序數(ordinal),可讀性較差。
Ref:JPA Column注解
若想存自己定義的 POJO 物件,那就要用「嵌入」(embed)的方式來處理。這種做法可讓我們程式中將「通用」的欄位抽出來,在各個 table 重複利用。比方說公司和員工都有聯絡方式(電話、信箱等);學生和老師都有職務(班級工作、行政工作等)。
以下是一個名為 Job 的類別,描述了職務。裡面的欄位有職務名稱(name
)和是否主要(primary
)。並且冠上了 @Embeddable
標記。
@Embeddable
public class Job {
private String name;
private Boolean primary;
public static Job of(String name, Boolean primary) {
var job = new Job();
job.name = name;
job.primary = primary;
return job;
}
// getter, setter ...
}
回到 StudentPO
這個 model 類別,添加 Job
物件欄位。並且冠上 @Embedded
標記,將具有 @Embeddable
的類別嵌入進來。這個標記相當於將 POJO 裡面的欄位「展開」。
public class StudentPO {
// ...
@Embedded
@AttributeOverride(name = "name", column = @Column(name = "job_name", length = 10))
@AttributeOverride(name = "primary", column = @Column(name = "job_primary"))
private Job job;
// ...
}
上面還使用了 @AttributeOverride
標記,它能讓我們在不同 model 類別各自設計 POJO 中的欄位。
@AttributeOverride
的 name
參數,是用來指向 POJO 類別中的欄位;而 column
參數則是用來設計該欄位,用法不變。
Ref:Jpa @Embedded and @Embeddable
我們可能想紀錄 table 中每一筆資料的建立和更新時間,或是操作的人。這時就要用上「JPA Auditing」,請先在啟動類別冠上 @EnableJpaAuditing
標記,以啟用此機制。
@SpringBootApplication
@EnableJpaAuditing
public class Application {
// ...
}
接著在 model 類別冠上 @EntityListeners
標記。
import jakarta.persistence.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(name = "student")
@EntityListeners(AuditingEntityListener.class)
public class StudentPO {
// ...
}
請宣告好日期時間欄位後,冠上 @CreatedDate
或 @LastModifiedDate
標記即可。
@EntityListeners(AuditingEntityListener.class)
public class StudentPO {
@Column(name = "created_time")
@CreatedDate
private LocalDateTime createdTime;
@Column(name = "updated_time")
@LastModifiedDate
private LocalDateTime updatedTime;
// ...
}
除了時間,我們也能紀錄建立或更新資料的操作者(auditor)。請宣告好欄位後,冠上 @CreatedBy
或 @LastModifiedBy
標記。
@EntityListeners(AuditingEntityListener.class)
public class StudentPO {
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "updated_by")
@LastModifiedBy
private String updatedBy;
// ...
}
JPA 雖然知道當下的日期時間,但在紀錄操作者時,並不知道是誰。關於這一點,要讓 AuditorAware
元件來告訴 JPA。那我們再接續建立一個實作 AuditorAware
介面的 Spring 元件。
@Component
public class DataModelAuditorAware implements AuditorAware<String> {
// Optional 的泛型隨介面連動
@Override
public Optional<String> getCurrentAuditor() {
// 只回傳隨機字串當作範例
var randomString = UUID.randomUUID().toString();
return Optional.of(randomString);
}
}
AuditorAware
接收一個泛型類別,它需要與那些冠上 @CreatedBy
或 @LastModifiedBy
的欄位的型態相同。由於 model 類別中的 createBy
和 updatedBy
欄位,型態為 String
,所以泛型也給予一樣的類別。
它的 getCurrentAuditor
方法,會在資料正要被存入 DB 時觸發,並將回傳值賦予給具有這兩個標記的欄位。此處只回傳一個隨機字串當範例。
讀者可視需要,在
AuditorAware
元件中,從 Spring Security Context 獲取當前使用者資訊。
當資料第一次儲存時(即建立),具有 @LastModifiedDate
和 @LastModifiedBy
的欄位其實也會同時被賦予值。而後續的儲存(即更新)才會只刷新這兩個欄位。
Ref:[Spring Data JPA] 自動存入時間和使用者
今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教