iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Software Development

救救我啊我救我!CRUD 工程師的惡補日記系列 第 9

【Spring Boot】使用 JPA 設計資料表欄位

  • 分享至 

  • xImage
  •  

在昨天的文章,我們設計了簡單的 model 類別,讓它對應到資料庫的 table。接著測試了 CRUD 流程。而本文將會介紹設計欄位的方式,包含名稱、長度、唯一性,嵌入物件欄位等等。

筆者使用的關聯式資料庫為 MySQL,但由於我們是面向 JPA 介面去實作,與底下使用哪一款 DB 並無關係,所以不會看到跟 MySQL 相關的程式。

此篇在 2024 年於「【Spring Boot】第8.2課-使用 JPA 設計實體類別與 MySQL 資料表欄位」文章更新。


一、Model 類別介紹

首先來看看本文要使用的 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),它提供一些用來設定的參數。

  • name:欄位名稱。這讓我們在修改 model 類別的欄位名稱時,也不會影響 table 欄位。
  • length:字串長度,超出的部份會捨棄。
  • nullable:是否可為空。
  • unique:是否唯一,意即整個 table 在該欄位不允許有重複的值。
  • precision / scale:適用於 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 欄位

若想存自己定義的 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 中的欄位。

@AttributeOverridename 參數,是用來指向 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 類別中的 createByupdatedBy 欄位,型態為 String,所以泛型也給予一樣的類別。

它的 getCurrentAuditor 方法,會在資料正要被存入 DB 時觸發,並將回傳值賦予給具有這兩個標記的欄位。此處只回傳一個隨機字串當範例。

讀者可視需要,在 AuditorAware 元件中,從 Spring Security Context 獲取當前使用者資訊。

當資料第一次儲存時(即建立),具有 @LastModifiedDate@LastModifiedBy 的欄位其實也會同時被賦予值。而後續的儲存(即更新)才會只刷新這兩個欄位。

Ref:[Spring Data JPA] 自動存入時間和使用者


今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教/images/emoticon/emoticon41.gif


上一篇
【Spring Boot】使用 JPA 串接 MySQL 資料庫
下一篇
【Spring Boot】使用 JPA 建立一對多關係
系列文
救救我啊我救我!CRUD 工程師的惡補日記50
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言