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

最近要處理服務的升級,主要的改變內容就是,許多的表格都要從單一欄位的 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 不正確了。

Leave a Reply

Your email address will not be published. Required fields are marked *

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