【Spring Boot】第8.2課-使用 JPA 設計實體類別與 MySQL 資料表欄位


https://unsplash.com/photos/LmyPLbbUWhA

在上一篇,我們了解可藉由定義實體類別,讓 Spring Data JPA 建立出資料表。而本文會介紹各種設定資料表欄位的方式,包含欄位名稱、長度與唯一性等。

此外也會共享具有相同設定的欄位,包含嵌入自定義物件,以及繼承基底類別的做法,達到重複使用。最後說明如何在插入或更新資料時,自動在欄位填入日期時間與使用者資料。

一、實體類別介紹

Spring Data JPA 允許我們在程式中,透過定義實體類別的方式,來設計資料表(table)。所謂的實體類別,指的是要儲存到資料庫的資料類別。

以下建立一個實體類別,描述了學生資料。

該類別叫做「Student」,目前包含 id、名字、年級、血型與生日,共 5 個欄位。

以下的「BloodType」是個列舉類別,包含 4 種血型。


二、設計資料表欄位

實體類別會對應到資料庫的 table,因此維護該類別,就相當於維護 table 的欄位。在實體類別設計欄位時,會搭配使用 @Column 這個注解,透過它的參數進行一些設定。

參數設定項目補充說明
nameTable 的欄位名稱針對同一個欄位,我們可以在實體類別與 table,分別使用不同的名稱。
length字串長度超出的部份會被截斷(truncate)。
nullable值是否可為 nullJava 基本型態預設為 false;參考型態預設為 true。
unique值是否唯一若為 true,會自動建立「唯一索引」。
precision整數與小數的總位數適用於 Java 的 BigDecimal 型態;MySQL 的 DECIMAL 型態。
scale小數在 precision 所佔的位數適用於 Java 的 BigDecimal 型態;MySQL 的 DECIMAL 型態。

以下是將 @Column 注解用在實體類別上。

另外,如果欄位型態是一個「列舉」(Enum),可冠上 @Enumerated 注解,設定將欄位值存為 Enum 值的名字。否則預設會以序數(ordinal)來儲存,可讀性較差。

在網路上其他文章中,讀者可能會注意到那些範例程式,即便實體類別的欄位名與 table 欄位一致,依然會再度使用 @Column 注解來設定 table 欄位名稱。筆者認為這樣的好處,是能避免日後在實體類別修改欄位名稱,無意間破壞與 table 的對應關係。

設定完成後,請讀者啟動程式,讓 Spring Data JPA 自動在資料庫產生 table。下圖是在 MySQL 使用 DESCRIBE 指令,確認 table 的定義。

mysql-workbench-describe-table-with-more-fields

在執行結果中,可看到欄位型態、哪個是唯一欄位,或者是否允許 null 值等資訊。

三、嵌入物件欄位

若想在實體類別中宣告自定義的物件欄位,那就得透過「嵌入」(embed)的方式來處理。

以下建立叫做「Contact」的類別,描述了聯繫方式。

此類別包含信箱與電話這 2 個欄位。為了嵌入到實體類別中,需冠上 @Embeddable 注解。它相當於是一個「共享類別」,除了學生,若日後還有其他實體類別(如老師、員工、公司等),均可重複使用。

接著回到 Student 實體類別,使用 @Embedded 注解嵌入 Contact 類別。

為了在實體類別對嵌入的物件欄位進行設定,需使用 @AttributeOverride 注解。它具有兩個參數,name 是指向內部的欄位;而 column 則是傳入前面介紹過的 @Column 注解,進行設定。


四、自動填入日期時間與使用者

(一)前言

我們可能會想記錄每一筆資料的異動資訊。比方說文章是何時發表的、何時修改的、誰建立的、誰更新的。透過 Spring Data 提供的「JPA Auditing」功能,可以幫助我們做這件事。

請先在啟動類別冠上 @EnableJpaAuditing 注解,以啟用此功能。

接著在實體類別類別冠上 @EntityListeners 注解,註冊這個功能。

(二)填入日期時間

以下在 Student 實體類別中,添加了 2 個欄位,分別代表資料的建立與更新時間。

只要在欄位分別冠上 @CreatedDate@LastModifiedDate 注解即可。Spring Data JPA 會在插入或更新資料時,將日期時間的值賦予給這些欄位。

當插入資料時,具有 @CreatedDate@LastModifiedDate 注解的欄位,都會同時被賦予值。而後續更新資料時,只會刷新 @LastModifiedDate 的欄位。

(三)填入使用者

以下在 Student 實體類別中,添加了 2 個欄位,分別代表資料的建立者與更新者。

接著在欄位分別冠上 @CreatedBy@LastModifiedBy 注解。Spring Data JPA 會在插入或更新資料時,將代表使用者的值賦予給這些欄位。

那麼使用者的資料從哪裡來呢?這時我們需要準備一個實作 AuditorAware 介面的元件,讓它告訴 Spring Data JPA。

這個 AuditorAware 介面接收一個泛型類別,它需要與那些冠上 @CreatedBy@LastModifiedBy 的欄位的型態相同。由於實體類別中的 createdByupdatedBy 欄位是字串,因此泛型類別應傳入 String。

接著覆寫 getCurrentAuditor 方法,它會在資料正要被儲存時,由 Spring Data JPA 觸發。其回傳值會被賦予給具有這兩種注解的欄位。此處回傳隨機字串,當作使用者的 id。

若讀者所開發的專案有引進 Spring Security,可參考「【Spring Boot】第17.4課-從 Security Context 取得 API 存取方的認證資訊」文章,在 AuditorAware 元件中,從 Security Context 獲取當前使用者資訊。

五、共享相同的欄位設定

(一)用於嵌入欄位

在本文第三節,我們在 Student 實體類別嵌入自定義的 Contact 類別,代表聯繫方式。

若將 Contact 也嵌入到其他實體類別,且內部欄位的設定均相同,那麼持續使用 @AttributeOverride 注解,就會寫出重複的內容。然而 Spring Data JPA 並不支援直接在內部使用 @Column 注解來設定。

為了重複利用欄位設定,我們首先需建立一個父類別,將要共享的欄位抽離過去。此外,為了強調該父類別是「基底類別」,不應被用來建立物件,故宣告為抽象。

該基底類別冠上了 @MappedSuperclass 注解,用途是讓子類別繼承後,能將父類別的欄位也對應到資料庫的 table 欄位上。

如此一來,在 Student 實體類別就不必透過 @AttributeOverride 注解來設定欄位值了。除非有少數特例需個別設定,否則可將其移除。

(二)用於實體類別

這種 @MappedSuperclass 注解,亦可用於那些實體類別都具備的欄位,例如 id、建立與更新資料的時間、建立與更新資料的人。

以下針對實體類別建立了一個基底類別。

而實體類別只要留下自身特有的欄位,並繼承該基底類別即可。一旦基底類別的設計有改動,則所有實體類別都會生效。


本文介紹了如何透過實體類別,設計資料庫中 table 的欄位,讀者可透過重啟程式,印證 table 的變化。但我們尚未確認本文第三節的 AuditorAware 元件,在插入或更新資料時的效果。下一篇將使用 Spring Data JPA 提供的介面,實際進行 CRUD。

本文的完成專案:
https://github.com/ntub46010/SpringBootTutorial/tree/Ch8-2

上一篇:【Spring Boot】第8.1課-準備 MySQL 資料庫與認識 Spring Data JPA

下一篇:【Spring Boot】第8.3課-使用 JPA Repository 存取 MySQL 資料庫

留言

  1. 這篇的完成專案連結是否寫錯了呢??

    回覆刪除
  2. 請問如果想要繼承多個基底類別,改用interface就可以了嗎? 謝謝~

    回覆刪除
    回覆
    1. Interface 沒辦法像類別那樣定義欄位

      作為替代方案,你可以先建立多個基底類別(假設有 A 和 B),再讓它們有繼承關係(如 B 繼承 A)。接著實體類別就能選擇要繼承簡單的 A,或欄位更多的 B。

      或者捨棄繼承的做法,把那些共同欄位「切小」成不同類別,再透過本文提到「嵌入」的做法,將那些欄位組合到實體類別中。

      刪除

張貼留言