操作 Spring Data JPA 需要對於資料表之間的關聯與配置有一定的了解,可以幫助你在規劃資料庫及撈取資料上有很大的幫助。

後續文章會依序介紹常見的幾種關聯操作,若這邊設計一個情境開設一個電商平台,賣電玩的商品,會有一些表紀錄商品的一些資訊,我們可以藉此來探討資料表之間的關聯。主要有下面這幾張表:

  • products 商品資訊,記錄名稱 (name)、價錢 (price)、描述 (description)等資訊
  • product_details 商品細節,紀錄開發商 (developer)、發行商 (publisher)、發行日 (release_date)、支援語言 (language_support)等資訊
  • reviews 評論,紀錄評論者 (reviewer_name)、內容 (comment)、分數 (rating)
  • tags 標籤,遊戲類型及各式分類標註 (name)
  • product_tags 商品及標籤的關聯表 (多對多需要)

https://ithelp.ithome.com.tw/upload/images/20240904/201509775cEGhSRpBL.png

關聯配置相關參數介紹

關聯時必須知道誰是父實體(或稱維護方,具有 FK 可以關聯被維護 PK),誰是子實體(被維護、被參考對象)

父實體(維護方) 子實體(被維護方)
關聯 具有 FK 被關聯 PK 或其他
註解應用區別 @JoinColumn() 關聯註解加入參數 mappedBy

Cascade

級聯配置參數,針對父實體的資料操作,子實體會有對應自動操作

  • CascadeType.ALL:無論儲存、更新、刷新或移除,一併對被參考物件作出對應動作。
  • CascadeType.PERSIST:父實體進行儲存 (repository 的 save) 操作,自動對被關聯子實體進行儲存操作
  • CascadeType.MERGE:父實體進行更新操作時,自動對關聯的子實體執行更新操作。
  • CascadeType.REFRESH:父實體進行刷新操作時,自動對關聯的子實體執行刷新操作。
  • CascadeType.REMOVE:父實體進行刪除操作時,自動對關聯的子實體執行刪除操作。

Fetch

資料讀取配置參數,設定讀取的方式是立即或是特定條件才讀

  • FetchType.EARGE:馬上加載子實體
  • FetchType.LAZY:只有應用到子實體的屬性時才會被加載

各類關聯註解預設值參考

Relation Default
@OneToOne FetchType.EARGE
@ManyToOne FetchType.EARGE
@OneToMany FetchType.LAZY
@ManyToMany FetchType.LAZY

一對一 1 : 1

一對一的關聯可以從 products 和 product_details 這兩張表之間的關聯看到,一個商品會對應一個詳細資料,要透過 JPA 會用到 @OneToOne 註解來進行關聯,這些關聯註解都有一些參數可以設置。在這邊的範例就是 products 是父實體,product_details 是子實體。

定義父實體 products

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@Data
@Table(name = "products")
//@JsonIgnoreProperties({"productDetail"}) // 避免循環引用
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
private String description;

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "product_detail_id", referencedColumnName = "id")
private ProductDetails productDetails;

}

@JoinColumn 的 name 參數指的是要提供 FK 的欄位名稱,referencedColumnName 是指被關聯方的 關聯欄位(不寫預設是被關聯的主鍵 PK)

定義子實體 product_details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Data
@Table(name = "product_details")
@JsonIgnoreProperties("product")
public class ProductDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String developer;
private String publisher;
private String releaseDate;
private String languageSupport;

@OneToOne(mappedBy = "productDetails")
private Product product;
}

這邊需要注意如果需要雙向關聯,這邊也要在關聯要呈現的屬性上面加上 @OneToOne(mappedBy = “{父方映射關聯的欄位}”),這邊也就是指父實體放 @OneToOne 欄位的變數名 productDetails

另外補充如果雙向關聯開啟,就會出現無限循環關聯的問題,你可以想像會出現 product 關聯出 product_details 但裡面又關聯回 product 就會一直永無止境導致回傳資料混亂,有幾個方法可以解決:

  1. 改用 DTO 將需要關聯資料裝入回傳
  2. @JsonIgnore 放在被關聯欄位上面就可以忽略關聯
  3. @JsonIgnoreProperties(”{關聯欄位變數名}”) 放在實體 class 上面

實際撈回來的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{
"id": 1,
"name": "最後生還者",
"price": 59.99,
"description": "由 Naughty Dog 開發的動作冒險遊戲。",
"productDetails": {
"id": 1,
"developer": "Naughty Dog",
"publisher": "Sony Interactive Entertainment",
"releaseDate": "2013-06-14",
"languageSupport": "English, Japanese, Chinese"
}
},
{
"id": 2,
"name": "巫師3",
"price": 49.99,
"description": "由 CD Projekt Red 開發的開放世界角色扮演遊戲。",
"productDetails": {
"id": 2,
"developer": "CD Projekt Red",
"publisher": "CD Projekt",
"releaseDate": "2015-05-19",
"languageSupport": "English, Chinese, Polish"
}
}
// 略......
]

以上就是基本一對一的關聯操作,下篇會來介紹一對多 (1 : N) 的操作模式。

這邊想要補充一下推薦有使用 Intellij 的 database UI ,連接好資料庫之後,對資料庫點右鍵選 Diagrams > Show Diagram 他就可以將各欄位之間的關聯狀況畫出來,還可以匯出圖片,就可以不用自己畫 ER 圖了,真的太強大 !!
https://ithelp.ithome.com.tw/upload/images/20240905/20150977RPC1MnpZSG.png


Ref: