Spring Repository复合主键自关联多对多保存报重复键错误求解
解决JPA自关联多对多关联时重复主键插入问题
1. 确保复合主键类符合JPA规范
复合主键类Resource_PK必须满足JPA的核心要求,否则JPA无法正确识别实体是否已存在:
- 实现
Serializable接口 - 基于主键字段重写
equals()和hashCode()方法,保证实体唯一性判断逻辑准确 - 提供无参构造函数(JPA反射实例化需要)
示例代码:
@Embeddable public class Resource_PK implements Serializable { private Long id; private String type; public Resource_PK() {} public Resource_PK(Long id, String type) { this.id = id; this.type = type; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Resource_PK that = (Resource_PK) o; return Objects.equals(id, that.id) && Objects.equals(type, that.type); } @Override public int hashCode() { return Objects.hash(id, type); } // getter/setter方法 }
2. 正确配置继承与自关联映射
针对Equipment继承Resource的场景,需明确继承策略,同时正确配置自关联多对多的关联表映射:
- 父类
Resource指定继承策略(推荐JOINED或SINGLE_TABLE) - 子类
Equipment的多对多关联需完整映射复合主键的所有字段
示例代码:
// 父类Resource @Entity @Inheritance(strategy = InheritanceType.JOINED) public class Resource { @EmbeddedId private Resource_PK id; // 其他通用字段 } // 子类Equipment @Entity public class Equipment extends Resource { @ManyToMany @JoinTable( name = "equipment_feeds", joinColumns = { @JoinColumn(name = "source_id", referencedColumnName = "id"), @JoinColumn(name = "source_type", referencedColumnName = "type") }, inverseJoinColumns = { @JoinColumn(name = "target_id", referencedColumnName = "id"), @JoinColumn(name = "target_type", referencedColumnName = "type") } ) private Set<Equipment> feeds = new HashSet<>(); @ManyToMany(mappedBy = "feeds") private Set<Equipment> fedBy = new HashSet<>(); // 封装关联操作,避免直接操作集合 public void addFeed(Equipment target) { this.feeds.add(target); target.getFedBy().add(this); } }
3. 操作处于持久化状态的实体
查询已存在的Equipment时,必须通过Spring Data Repository的findById()或EntityManager的find()方法获取,确保实体处于JPA持久化上下文的跟踪中:
// 正确:从仓库查询已存在的持久化实体 Equipment eq1 = equipmentRepository.findById(new Resource_PK(1L, "EQUIP")).orElseThrow(); Equipment eq2 = equipmentRepository.findById(new Resource_PK(2L, "EQUIP")).orElseThrow(); eq1.addFeed(eq2); // 实体处于持久化状态时,JPA会自动同步关联表,无需调用saveAll equipmentRepository.save(eq1);
错误做法:使用手动创建或从DTO转换的分离实体,这类实体不在持久化上下文跟踪中,JPA会误判为新实体执行插入。
4. 处理分离实体的关联操作
如果实体是从缓存、DTO转换等场景得到的分离实体,需先通过EntityManager.merge()将其合并到当前持久化上下文,再进行关联操作:
// 假设eq1、eq2是分离实体 Equipment managedEq1 = entityManager.merge(eq1); Equipment managedEq2 = entityManager.merge(eq2); managedEq1.addFeed(managedEq2); equipmentRepository.save(managedEq1);
5. 验证Spring Data的save逻辑
Spring Data的save()方法会根据实体状态判断执行插入或更新:
- 若实体主键存在且JPA通过
equals()/hashCode()识别为已持久化,执行更新 - 若主键逻辑错误,会导致JPA误判为新实体触发插入,因此必须保证复合主键的
equals()/hashCode()逻辑准确
内容的提问来源于stack exchange,提问作者Skip
相关产品推荐
相关产品推荐

