JPA PK 轉換成 composite key 後 foreign key 要注意的小細節

最近要處理服務的升級,主要的改變內容就是,許多的表格都要從單一欄位的 PK,改變成複數欄位的 PK。
除了資料庫的異動之外,當然就是 JPA 的 model 要做相對應的調整。

最近要處理服務的升級,主要的改變內容就是,許多的表格都要從單一欄位的 PK,改變成複數欄位的 PK。

除了資料庫的異動之外,當然就是 JPA 的 model 要做相對應的調整。

而在這個調整過程裡面,有一些小細節隨著一些小問題被挑出來,可以當作是未來在開發時的注意事項。以下是舉例說明:

這是本來的表格,Game 表格有個單獨欄位的 PK 名稱是 sn,會 auto increment;而 Photo 這個表格則有一個 FK 會關聯到這個 Game 表格;他們之間是一對多的關係(Game 1:n Photo)。

在 JPA 裡面這個關係很容易描述,如果不做雙向的關係,只需要在 Photo 這個 Entity 裡面這樣描述就可以:

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
@JoinColumn(name = "game_sn")
public Game getGame() {
	return game;
}

然後當你在新增 Photo 這個物件時,把取得的 Game 物件塞給他,再丟給 JPA 就可以看到在資料庫新增成功了,這部分就不再贅述。(有機會的話再講很基本的 JPA 開發好了)

在這個時候,Game 這個 Entity 是很簡單的,而且通常我都會偷懶:

以上都運作正常,接下來我們要開始做調整,將 Game 這個表格變成是複合欄位 PK:

所以 Photo 表格跟著加上欄位,並且相對應的 FK 做調整。

這時候 JPA 的 Game Entity 要跟著改,先建立一個 GamePk 的物件,帶有兩個 property:

@Embeddable
public class GamePk implements java.io.Serializable {

	private Integer sn;

	@Column(name = "organization_sn")
	private Integer organizationSn;

	public Integer getSn() {
		return sn;
	}

	public void setSn(Integer sn) {
		this.sn = sn;
	}

	public Integer getOrganizationSn() {
		return organizationSn;
	}

	public void setOrganizationSn(Integer organizationSn) {
		this.organizationSn = organizationSn;
	}

}

Game Entity 本來的 @Id 也要跟著改,這部分就不贅述。然後 Photo Entity 也要跟著改:

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
@JoinColumns(
{ 
@JoinColumn(name = "game_sn", referencedColumnName = "sn"),
@JoinColumn(name = "organization_sn", referencedColumnName = "organization_sn") 
}
)
public Game getGame() {
	return game;
}

簡單的說就是要變成複合欄位的樣貌。

本來的流程不變,一樣是取得 Game 物件後,塞給 Photo 物件,然後再透過 JPA 去新增一筆資料。

結果發現,Photo 表格的 game_sn 欄位與 organization_sn 欄位沒有資料新增進去;把 JPA 底層用的 Hibernate 將 SQL 列印出來,發現 SQL 的確沒要求新增資料到這兩個欄位。

所以是哪裡錯了?

是這裡,在 GamePk 的 sn property 上面,少宣告了 @Column 這個 annotation。

@Embeddable
public class GamePk implements java.io.Serializable {

	@Column(name = "sn")
	private Integer sn;

	@Column(name = "organization_sn")
	private Integer organizationSn;

	public Integer getSn() {
		return sn;
	}

	public void setSn(Integer sn) {
		this.sn = sn;
	}

	public Integer getOrganizationSn() {
		return organizationSn;
	}

	public void setOrganizationSn(Integer organizationSn) {
		this.organizationSn = organizationSn;
	}

}

在 JPA 裡面,如果你的 property 名稱剛好與資料庫表格欄位名稱相同時,其實是可以省略這個 @Column 的宣告的,而殊不知,在複合欄位 PK 這個地方,你省略了這個步驟,就導致底層的 SQL 不正確了。

Mark Su

熱愛籃球、程式設計與美食。

Add comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Follow me

Don't be shy, get in touch. I love meeting interesting people and making new friends.