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

