1. 引言
在本篇文章中,我们将学习 JPA(Java Persistence API)中复合主键(Composite Primary Key)的使用方式,以及两个核心注解 @IdClass 和 @EmbeddedId 的区别和适用场景。
2. 什么是复合主键
复合主键指的是由多个字段共同组成一个表的主键。在数据库设计中,这种结构常用于表示多对多关系中的关联表或某些业务逻辑上必须联合唯一标识记录的场景。
在 JPA 中,我们可以通过两种方式来定义复合主键:
✅ 使用 @IdClass
✅ 使用 @EmbeddedId
无论使用哪种方式,复合主键类都必须满足以下条件:
- 必须是 public 类
- 必须实现 Serializable 接口
- 必须包含无参构造函数
- 必须重写 equals() 和 hashCode() 方法
3. 使用 @IdClass 注解
我们以一个 Account 表为例,假设它的主键由 accountNumber 和 accountType 两个字段组成。
首先,我们需要创建一个复合主键类 AccountId:
public class AccountId implements Serializable {
private String accountNumber;
private String accountType;
public AccountId() {
}
public AccountId(String accountNumber, String accountType) {
this.accountNumber = accountNumber;
this.accountType = accountType;
}
// equals() and hashCode()
}
接下来,我们定义实体类 Account,并使用 @IdClass 注解指定主键类:
@Entity
@IdClass(AccountId.class)
public class Account {
@Id
private String accountNumber;
@Id
private String accountType;
// other fields, getters and setters
}
⚠️ 注意:这里的主键字段需要在实体类中重复声明,并且每个字段都加上 @Id 注解。
4. 使用 @EmbeddedId 注解
另一种方式是使用 @EmbeddedId,它将主键类作为一个嵌入对象直接包含在实体中。
假设我们有一个 Book 实体,其主键为 title 和 language。
首先定义主键类 BookId,并加上 @Embeddable 注解:
@Embeddable
public class BookId implements Serializable {
private String title;
private String language;
public BookId() {
}
public BookId(String title, String language) {
this.title = title;
this.language = language;
}
// getters, equals(), hashCode()
}
然后在实体类中使用 @EmbeddedId 引用该主键类:
@Entity
public class Book {
@EmbeddedId
private BookId bookId;
// constructors, other fields, getters and setters
}
这种方式更加面向对象,且主键类只需定义一次,避免了字段重复声明的问题。
5. @IdClass 与 @EmbeddedId 的对比
特性 | @IdClass | @EmbeddedId |
---|---|---|
主键字段是否需要重复声明 | ✅ 是 | ❌ 否 |
JPQL 查询更简洁 | ✅ 是 | ❌ 否 |
是否适合已有不可修改的主键类 | ✅ 是 | ❌ 否 |
是否适合频繁操作整个主键对象 | ❌ 否 | ✅ 是 |
简单来说:
- 如果你需要频繁访问主键中的个别字段,或者主键类是你不能修改的第三方类,那么推荐使用 @IdClass。
- 如果你更倾向于面向对象的方式操作主键整体,或者经常需要将主键作为一个对象传递或比较,推荐使用 @EmbeddedId。
6. 总结
本文介绍了 JPA 中复合主键的两种实现方式:*@IdClass* 和 @EmbeddedId,并分析了它们的适用场景和优缺点。
选择哪种方式取决于你的实际需求和项目结构。在实际开发中,建议根据业务复杂度和代码可维护性来做出权衡。
完整的示例代码可以在 GitHub 上找到。