You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 13:39:02