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

Spring JPA使用OneToMany关联时出现Duplicate entry键重复报错如何解决

问题原因
  1. 关联关系类型选择错误:用户(user)和职位(job)的申请关系是典型的多对多关系,即一个用户可以申请多个职位,一个职位可以接收多个用户的申请。你当前在两边都配置了@OneToMany,不符合业务语义,导致Hibernate生成的中间表自带唯一约束:
    • 你在Job类的candidates字段配置@OneToMany,Hibernate默认会生成job_candidates中间表,并且给user_id字段添加唯一约束(因为@OneToMany的语义是「一个用户只能属于一个职位的候选列表」,和你的实际业务冲突)
    • 当你重复给同一个职位添加同一个用户,或者同一个用户申请多个职位时,就会触发唯一键冲突报错,也就是你看到的Duplicate entry '2' for key 'job_candidates.UK_xxx'错误
  2. 操作冗余:当前业务代码里分别修改两边的关联列表再分别保存,属于冗余操作,双向关联只要合理配置,不需要重复操作。
解决方案

第一步:修正关联关系注解,改用@ManyToMany

双向多对多需要指定一方为关系维护端,另一方配置mappedBy指向维护端的字段名,避免生成重复的中间表。这里选择User作为关系维护端:

修正后的User实体代码

@Entity
public class User { // 建议类名首字母大写,符合Java编码规范
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String username;
    private String password;
    private String role;
    @ManyToOne(cascade=CascadeType.ALL)
    private File f; // 类名首字母大写

    @JsonIgnore
    @ManyToMany(targetEntity = Job.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // 不要配置CascadeType.ALL,避免删除职位时级联删除关联用户
    @JoinTable(
        name = "user_job_apply", // 自定义中间表名,语义更清晰
        joinColumns = @JoinColumn(name = "user_id"), // 维护端外键名
        inverseJoinColumns = @JoinColumn(name = "job_id") // 被维护端外键名
    )
    private List<Job> applied;

    // 原有getters、setters、构造方法保留
}

修正后的Job实体代码

@Entity
public class Job { // 类名首字母大写
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    private String role;
    private String company;
    private String salary;
    private String description;
    private String level;
    private Date expiry;
    private String skills;
    private String location;
   
    @JsonIgnore
    @ManyToMany(mappedBy = "applied", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) // mappedBy指向User实体里的applied字段,表示这是关系被维护端
    private List<User> candidates;

    // 原有getters、setters、构造方法保留
}

第二步:简化业务代码

双向多对多只需要维护关系维护端的关联即可,同时添加重复申请校验避免异常:

@Transactional // 必须加事务注解,避免懒加载异常和事务提交问题
public String applyToJob(Integer userId,Integer jobId) {
    // 用findById替代getOne/getById,避免懒加载代理对象操作异常
    Job jobToApply = jobRepo.findById(jobId).orElseThrow(() -> new RuntimeException("职位不存在"));
    User applier = userRepo.findById(userId).orElseThrow(() -> new RuntimeException("用户不存在"));
    // 先判断是否已经申请过,避免重复添加
    if (!applier.getApplied().contains(jobToApply)) {
        applier.getApplied().add(jobToApply);
        // 可选:维护被维护端的关联,方便后续在Job对象里直接拿到最新的候选列表,不会额外入库
        jobToApply.getCandidates().add(applier);
        userRepo.save(applier);
    }
    return "Job Apply Success";
}

第三步:清理旧表

重启服务前先删除之前Hibernate自动生成的user_applied、job_candidates旧中间表,让Hibernate重新生成符合多对多规则的user_job_apply新表即可。

内容的提问来源于stack exchange,提问作者Reeshav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 11:36:05