首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JPA 複合主鍵完整映射指南:@EmbeddedId vs @IdClass 實戰比較

     編輯:Java 相關知識

複合主鍵映射兩大模式

JPA 提供兩種標準方式映射複合主鍵:

  1. @EmbeddedId:主鍵類內嵌於 Entity,物件導向直覺。
  2. @IdClass:主鍵類分離,Entity 以多個 @Id 欄位對應,適合既有資料庫或需獨立重用 PK 類。

本文以「小時統計表」為例,PK = (stat_date, path_id, type),完整示範兩種寫法與取捨。

模式一:@EmbeddedId (推薦新專案)

1. 主鍵類 - 必須實作 Serializable、equals/hashCode

@Embeddable
public class HourDataPK implements Serializable {
    @Column(name = "stat_date")
    @Temporal(TemporalType.DATE)
    private LocalDate statDate;          // 建議用 Java 8 Time API

    @Column(name = "path_id")
    private Long pathId;

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

    // 無參建構子、getter/setter、equals/hashCode 略
    // equals/hashCode 必須包含所有 PK 欄位
}

2. Entity 類

@Entity
@Table(name = "TB_HOUR_DATA")
public class HourData implements Serializable {
    @EmbeddedId
    private HourDataPK pk;               // 單一屬性持有複合 PK

    @Column(name = "value", length = 512)
    private String value;

    // 業務便利方法
    public int[] getHours() { /* 解析 value 為 int[24] */ }
    public void setHours(int[] hours) { /* 組裝 value */ }

    // getter/setter 略
}

3. 使用範例

// 查詢
HourDataPK pk = new HourDataPK();
pk.setStatDate(LocalDate.of(2024, 1, 15));
pk.setPathId(1001L);
pk.setType(1);
HourData data = em.find(HourData.class, pk);

// 新增
HourData newData = new HourData();
HourDataPK newPk = new HourDataPK();
newPk.setStatDate(LocalDate.now());
newPk.setPathId(2002L);
newPk.setType(2);
newData.setPk(newPk);
newData.setValue("0,1,2,3,...");
em.persist(newData);

模式二:@IdClass (適合既有結構)

// PK 類不變,但不加 @Embeddable
public class HourDataPK implements Serializable {
    private LocalDate statDate;
    private Long pathId;
    private Integer type;
    // equals/hashCode 同前
}

@Entity
@Table(name = "TB_HOUR_DATA")
@IdClass(HourDataPK.class)             // 指定 PK 類
public class HourData implements Serializable {
    @Id
    @Column(name = "stat_date")
    @Temporal(TemporalType.DATE)
    private LocalDate statDate;

    @Id
    @Column(name = "path_id")
    private Long pathId;

    @Id
    @Column(name = "type")
    private Integer type;

    @Column(name = "value", length = 512)
    private String value;
    // getter/setter 略
}

差異:查詢時 em.find(HourData.class, pk) 依然可用,但 Entity 內部沒有單一 pk 屬性,需自行組裝 PK 物體。

進階議題

1. 複合主鍵包含關聯 (@ManyToOne / @OneToOne)

@Embeddable
public class OrderItemPK implements Serializable {
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
    // equals/hashCode 需比較 order.id + product.id
}

⚠️ 陷阱:PK 類若含關聯,equals/hashCode 必須比較關聯的 主鍵值 而非物件參考,否則 em.find() 會失效。

2. Spring Data JPA Repository 介面

public interface HourDataRepository extends JpaRepository {
    // 自動支援 findById(HourDataPK)、existsById、deleteById
    List findByPkStatDateBetween(LocalDate start, LocalDate end);
    @Query("select h from HourData h where h.pk.pathId = :pid and h.pk.type = :type")
    List findByPathAndType(@Param("pid") Long pid, @Param("type") Integer type);
}

3. 遷移建議:從舊式 @IdClass 升級 @EmbeddedId

  1. 建立 @Embeddable PK 類,複製原 @IdClass 欄位。
  2. Entity 改用單一 @EmbeddedId 屬性,移除多個 @Id。
  3. 資料庫結構完全不變,僅 Java 端重構。
  4. 測試 find/insert/update/delete 全流程。

快速選型表

場景推薦理由
新專案、綠地開發@EmbeddedId物件導向、單一 PK 物件、程式碼乾淨
既有資料庫、大量原生 SQL、PK 類需共用@IdClassEntity 結構接近資料表、PK 類可獨立引用
PK 含關聯 (@ManyToOne)@EmbeddedId關聯導航更自然
Spring Data JPA皆可Repository 介面自動適配兩種模式

結語

複合主鍵映射的核心在於 PK 類正確實作 equals/hashCode/Serializable 以及 欄位對應精確。@EmbeddedId 更符合現代 JPA 風格,建議新專案優先採用;既有專案若無痛點,@IdClass 也能穩定運行。選定後貫徹專案規範,避免混用造成認知負擔。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved