2025-02-17

考慮兩個具有一對一關聯的實體類,受控方除瞭有一個自己的主鍵,還有一個引用主控方的外鍵。如果主控方和受控方是同生同滅的關系,換句話說,雙方的一對一關聯一旦確立就不可更改,就可以考慮讓雙方共享相同的主鍵,簡化受控方的表結構。下面就讓樓主通過實例來說明如何用 JPA 2.0 來實現這種映射。

盯著眼前的電腦,樓主想到瞭一個也許不太貼切的例子:員工和公司配的電腦的關系。員工的主鍵就是工號。雖然電腦可能被換掉,但電腦實體完全可以用工號做主鍵,隻是把電腦配置的詳細信息改掉而已。此處的難點就在與如何將電腦的主鍵字段同時映射一個員工,請看樓主是怎麼一步步推導出來的。

一開始是最想當然的寫法:

?1
2
3
4
5
6 public class Computer implements Serializable {
    @Id
    @OneToOne
    private Employee employee;
    // 此處省略若幹行

然而根據規范,隻有這些類型可以作為主鍵:Java 原始類型(例如 int)、原始包裝類型(例如 Integer)、String、java.util.Date、java.sql.Date、 java.math.BigDecimal 和 java.math.BigInteger。所以直接拿 Employee 做主鍵是不行的。順便提一下,也許某些 JPA 實現自己做瞭擴展,使得可以直接拿實體類做主鍵,這已經超出瞭 JPA 規范的范疇,此處不討論。

直接映射是行不通瞭,那有什麼間接的方式嗎?這時樓主想起瞭一個特殊的註解:EmbeddedId。該註解的本意是用於聯合主鍵,不過變通一下,是否可以將 Employee 包裝進一個嵌入式主鍵,然後再將這個嵌入式主鍵作為 Computer 的主鍵以達到目的?帶著這種想法,樓主有瞭下面的代碼:

?1
2
3
4
5
6 @Embeddable
public class ComputerId implements Serializable {
    @OneToOne
    private Employee employee;
    // 此處省略若幹行

?1
2
3
4
5 public class Computer implements Serializable {
    @EmbeddedId
    private ComputerId id;
    // 此處省略若幹行

現在又出現瞭新的問題:JPA 不支持定義在嵌入式主鍵類中的關聯映射。好在天無絕人之路,EmbeddedId 的文檔中直接指出,可以使用 MapsId 註解來間接指定嵌入式主鍵類中的關聯映射,而且還附帶瞭一個例子!於是最終的成品就出爐瞭:

?1
2
3
4
5 @Embeddable
public class ComputerId implements Serializable {
    private String employeeId;
    // 此處省略若幹行

?1
2
3
4
5
6
7
8 public class Computer implements Serializable {
    @EmbeddedId
    private Employee employee;
    @MapsId("employeeId")
    @OneToOne
    private Employee employee;
    // 此處省略若幹行

唯一的遺憾是,邏輯上的主控方 Employee 現在不得不成為受控方瞭,好在可以定義級聯操作來達到差不多的效果:

?1
2
3
4
5
6
7 public class Employee implements Serializable {
    @Id
    private String id;
    @OneToOne(mappedBy = "employee", cascade = CascadeType.ALL)
    private Computer computer;
    // 此處省略若幹行

雖然做到瞭,但確實挺繞的。希望未來版本的 JPA 能直接支持將實體類作為主鍵,樓主個人覺得不是一個技術問題。

作者“神奇好望角”
 
 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *