JPA一对多关联添加子实体时出现主键重复异常
使用Java 21、Spring Boot 3+和Spring Data JPA实现User与Authority一对多关联时,保存Authority子实体时Hibernate尝试插入已存在的User,触发SQLIntegrityConstraintViolationException主键重复异常。
核心原因分析
- AuthorityKey的equals方法逻辑错误:判断条件写反,导致Hibernate无法正确识别关联User的持久态身份,误将其判定为需要插入的瞬时态实体。
- 复合主键映射不规范:使用
@IdClass时,主键类中的关联字段直接使用User实体而非对应主键类型,JPA无法正确解析关联关系的状态。 - 游离态实体关联问题:在事务外部查询的User处于游离态,事务内关联到新Authority时,Hibernate无法识别其已存在于数据库中。
解决方案
1. 修复AuthorityKey的equals方法逻辑
原代码中equals方法的判断逻辑完全颠倒,修正后:
public class AuthorityKey implements Serializable { private User user; private AuthorityType authority; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AuthorityKey)) return false; AuthorityKey authorityKey = (AuthorityKey) o; if (!Objects.equals(user, authorityKey.user)) return false; return authority == authorityKey.authority; } @Override public int hashCode() { return Objects.hash(user, authority); } }
2. 规范复合主键映射(推荐方案)
将AuthorityKey中的关联字段改为User的主键类型,并通过@MapsId关联映射:
修改AuthorityKey
public class AuthorityKey implements Serializable { // 对应User的主键id,字段名需与Authority中user属性匹配 private String user; private AuthorityType authority; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AuthorityKey)) return false; AuthorityKey that = (AuthorityKey) o; return Objects.equals(user, that.user) && authority == that.authority; } @Override public int hashCode() { return Objects.hash(user, authority); } }
修改Authority实体的关联映射
@Entity @IdClass(AuthorityKey.class) @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class Authority { @Id @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @MapsId("user") // 关联AuthorityKey中的user字段 private User user; @Id @Enumerated(EnumType.STRING) private AuthorityType authority; // equals、hashCode、toString方法保持不变 }
3. 确保User在事务内处于持久态
将User的查询逻辑移至事务内部,避免游离态实体引发的状态误判:
@Service public class AuthorityService { public void assignAuthoritiesToUser(String userId, Set<AuthorityType> authorities) { transactionTemplate.execute(status -> { User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found")); Set<AuthorityType> authoritiesToAssign = new HashSet<>(authorities); Set<AuthorityType> existingAuthorityTypes = authorityRepository.findAuthorityTypesByUserId(userId); if (!existingAuthorityTypes.isEmpty()) { authoritiesToAssign.removeAll(existingAuthorityTypes); } authoritiesToAssign.stream().map(authorityValue -> { Authority authority = new Authority(); authority.setAuthority(authorityValue); authority.setUser(user); return authority; }) .forEach(authorityRepository::save); return null; }); } }
验证
执行测试用例shouldAssignAuthoritiesToUser,Hibernate将仅插入新的Authority记录,不再重复插入已存在的User,主键冲突异常消失。
内容的提问来源于stack exchange,提问作者Rando Shtishi
相关产品推荐
相关产品推荐

