如何在JPA中为实体定义复合主键中的关联实体字段
在JPA中将复合主键字段替换为关联实体的规范实现
你的当前实现是否符合规范?
你的现有写法是符合JPA规范的:
- 保留了复合主键要求的
userId字符串字段,满足@IdClass的序列化和主键约束规则 - 通过
@ManyToOne关联User实体,同时设置insertable = false, updatable = false,避免了同一数据库列(user_id)被两个字段重复映射引发的持久化冲突
不过这种方式存在冗余字段(userId和user对应同一列),可以用更优雅的规范写法优化。
标准实现方案
方案1:基于@IdClass + @MapsId优化(去掉冗余字段)
用@MapsId注解直接把关联实体的主键映射为复合主键的一部分,不用再保留冗余的userId字符串字段:
@Getter @Setter @Entity @Table(name = "rank_season") // 需指定表名,与DDL对应 @IdClass(GameSeason.Pk.class) public class GameSeason { @Id private LocalDate startDate; // 既是复合主键的一部分,又是关联User实体的字段 @Id @ManyToOne(fetch = FetchType.LAZY) // 建议添加LAZY延迟加载,提升性能 @JoinColumn(name = "user_id", nullable = false) @MapsId("userId") // 指明映射到Pk类中的userId属性 private User user; private BigInteger prizeTop; // 主键类保持简单类型组合,对应实体的主键字段类型 @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode public static class Pk implements Serializable { private LocalDate startDate; private String userId; // 与User实体的主键类型一致 } }
方案2:使用@EmbeddedId(更面向对象的写法)
用@EmbeddedId替代@IdClass,把复合主键封装成嵌入式对象,再通过@MapsId关联User实体:
第一步:定义嵌入式复合主键类
@Getter @Setter @Embeddable @EqualsAndHashCode public class GameSeasonPk implements Serializable { private LocalDate startDate; private String userId; // 对应User的主键字段 }
第二步:改造GameSeason实体
@Getter @Setter @Entity @Table(name = "rank_season") public class GameSeason { @EmbeddedId private GameSeasonPk id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false) @MapsId("userId") // 把User的主键同步到GameSeasonPk的userId字段 private User user; private BigInteger prizeTop; // 方便创建实例的构造方法 public GameSeason(LocalDate startDate, User user) { this.id = new GameSeasonPk(); this.id.setStartDate(startDate); this.id.setUserId(user.getUserId()); this.user = user; } // JPA必须的无参构造 protected GameSeason() {} }
两种方案怎么选?
- 如果你已经有基于
@IdClass的代码,不想大改,选方案1,迁移成本低 - 要是新项目或者允许重构代码,选方案2,结构更清晰,更符合面向对象设计
内容的提问来源于stack exchange,提问作者Joonseo Lee
相关产品推荐
相关产品推荐

