JPA OneToMany场景父复合主键作为子主键组成部分时实体抛出异常问题
问题根因汇总
- 子实体
EventDetailsModel未标注@IdClass,JPA无法识别复合主键规则 - 两个复合主键类
ParticipantKey、ParticipantEventDetailsId未实现equals()和hashCode()方法,不符合JPA复合主键强制规范 - 父实体类
game_year属性赋值用了单引号,属于Java语法错误 - Controller逻辑冗余:已经配置了父实体级联保存规则,无需手动持久化子实体;循环中调用
entityManager.clear()会导致持久上下文失效,触发主键缺失异常 - 子实体上
@ManyToOne注解加了CascadeType.ALL,会触发子实体反向操作父实体的异常 - 实体类未补全JSON入参中的对应字段(
name、major_event_code),会导致反序列化丢值
修正方案
1. 修正父实体主键类
public class ParticipantKey implements Serializable { private Long pt_reg_no; private String game_year; // 必须实现无参构造 public ParticipantKey() {} public ParticipantKey(Long pt_reg_no, String game_year) { this.pt_reg_no = pt_reg_no; this.game_year = game_year; } // 必须重写equals和hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ParticipantKey that = (ParticipantKey) o; return Objects.equals(pt_reg_no, that.pt_reg_no) && Objects.equals(game_year, that.game_year); } @Override public int hashCode() { return Objects.hash(pt_reg_no, game_year); } }
2. 修正子实体主键类
public class ParticipantEventDetailsId implements Serializable { // 字段名必须和子实体中@Id标注的关联字段名完全一致,类型为父实体主键类型 private ParticipantKey participantModel; private String sub_event_code; private String category_code; public ParticipantEventDetailsId() {} @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ParticipantEventDetailsId that = (ParticipantEventDetailsId) o; return Objects.equals(participantModel, that.participantModel) && Objects.equals(sub_event_code, that.sub_event_code) && Objects.equals(category_code, that.category_code); } @Override public int hashCode() { return Objects.hash(participantModel, sub_event_code, category_code); } }
3. 修正父实体ParticipantModel
@Entity @IdClass(ParticipantKey.class) public class ParticipantModel { @Id @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "participantseq" ) @SequenceGenerator( name = "participantseq", allocationSize = 1 ) private Long pt_reg_no; @Id // 单引号改为双引号,符合Java字符串语法 private String game_year = "G12021"; // 补全JSON入参对应字段 private String name; @OneToMany(mappedBy = "participantModel", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) private Set<EventDetailsModel> eventDetails = new HashSet<>(); public Set<EventDetailsModel> getEventDetails() { return eventDetails; } public void setEventDetails(Set<EventDetailsModel> eventDetails) { this.eventDetails = eventDetails; for (EventDetailsModel b : eventDetails) { b.setParticipantModel(this); } } // 其余getter、setter自行补全 }
4. 修正子实体EventDetailsModel
@Entity // 新增@IdClass指定复合主键类 @IdClass(ParticipantEventDetailsId.class) public class EventDetailsModel { @Id @Column(name = "sub_event_code") private String sub_event_code; @Id @Column(name = "category_code") private String category_code; // 补全JSON入参对应字段 private String major_event_code; @Id @ManyToOne // 移除多余的CascadeType.ALL,避免子实体反向操作父实体 @JoinColumns({ @JoinColumn(name = "pt_reg_no", referencedColumnName = "pt_reg_no"), @JoinColumn(name = "game_year", referencedColumnName = "game_year") }) private ParticipantModel participantModel; // 其余getter、setter自行补全 }
5. 简化Controller逻辑
@RequestMapping(path = "/participantData", method = RequestMethod.POST) @Transactional public ResponseEntity<?> participantRegistration(@Valid @RequestBody FormDataObjects values) { ParticipantModel participant = values.getFormData(); participant.setGame_year(new SimpleDateFormat("yyyy").format(new Date()) + GenricData.game); // 直接保存父实体即可,级联配置会自动同步保存所有关联的子实体 participantRepository.save(participant); return ResponseEntity.ok("inserted"); }
内容的提问来源于stack exchange,提问作者Bapuji Nakka
相关产品推荐
相关产品推荐

