如何在JPA Entity中定义由两个@OneToOne字段组成的复合主键
实现方案
你提出的需求可以实现,之前使用@PrimaryKeyJoinColumn失败是因为该注解主要用于单主键的派生主键场景,复合主键场景需要配合@IdClass或@EmbeddedId方案实现,以下是两种可行的实现方式:
方案一:@IdClass 实现(更贴合你的使用习惯)
该方案可以直接将@OneToOne关联字段作为主键的组成部分,结构更直观。
- 首先定义复合主键类
import java.io.Serializable; import java.util.Objects; // 复合主键类必须满足三个要求:实现Serializable、重写equals和hashCode、存在无参构造 public class EmployeeId implements Serializable { // 字段名必须和EmployeeEntity中标注@Id的关联字段名完全一致 private PersonEntity person; private DepartmentEntity department; public EmployeeId() {} public EmployeeId(PersonEntity person, DepartmentEntity department) { this.person = person; this.department = department; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EmployeeId that = (EmployeeId) o; return Objects.equals(person, that.person) && Objects.equals(department, that.department); } @Override public int hashCode() { return Objects.hash(person, department); } }
- 调整EmployeeEntity实体配置
import javax.persistence.*; @Entity @Table(name = "EMPLOYEE") @IdClass(EmployeeId.class) // 绑定复合主键类 public class EmployeeEntity { @Id @OneToOne @JoinColumn(name = "PERSON_ID", referencedColumnName = "ID", nullable = false) private PersonEntity person; @Id @OneToOne @JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "ID", nullable = false) private DepartmentEntity department; // 其他业务字段、getter、setter方法 }
该方案自动生成的表结构和你期望的完全一致,两个字段共同作为主键,同时分别为关联PERSON、DEPARTMENT表的外键。
方案二:@EmbeddedId 实现
该方案将主键字段和关联字段分离,适合主键包含非关联字段的场景。
- 定义嵌入式主键类
import javax.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; @Embeddable public class EmployeeId implements Serializable { @Column(name = "PERSON_ID", nullable = false) private Long personId; @Column(name = "DEPARTMENT_ID", nullable = false) private Long departmentId; public EmployeeId() {} public EmployeeId(Long personId, Long departmentId) { this.personId = personId; this.departmentId = departmentId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EmployeeId that = (EmployeeId) o; return Objects.equals(personId, that.personId) && Objects.equals(departmentId, that.departmentId); } @Override public int hashCode() { return Objects.hash(personId, departmentId); } }
- 调整EmployeeEntity实体配置
import javax.persistence.*; @Entity @Table(name = "EMPLOYEE") public class EmployeeEntity { @EmbeddedId private EmployeeId id; @OneToOne @MapsId("personId") // 关联主键类中的personId字段 @JoinColumn(name = "PERSON_ID", insertable = false, updatable = false) private PersonEntity person; @OneToOne @MapsId("departmentId") // 关联主键类中的departmentId字段 @JoinColumn(name = "DEPARTMENT_ID", insertable = false, updatable = false) private DepartmentEntity department; // 其他业务字段、getter、setter方法 }
注意事项
- 保存
EmployeeEntity前必须先保存PersonEntity和DepartmentEntity,保证关联对象处于持久化状态 - 若使用Spring Data JPA,Repository接口的主键泛型需要替换为对应的复合主键类,示例:
public interface EmployeeRepository extends JpaRepository<EmployeeEntity, EmployeeId>
内容的提问来源于stack exchange,提问作者Oleg Leinov
相关产品推荐
相关产品推荐

