首页 > 解决方案 > 如何在使用spring数据和jpa hibernate保存具有一对多关系的实体时避免唯一键违规?

问题描述

我在保存两个具有“一对多”关系的实体时遇到问题。我得到了 ConstraintViolationException:

违反 UNIQUE KEY 约束“...”。无法在对象“dbo.classification”中插入重复键。重复键值为 (...)。

这是我的实体:

@Data
@Table(uniqueConstraints = {
        @UniqueConstraint(columnNames = {"x", "y"})
})
@Entity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class Account {

    @Id
    @Nullable
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(unique = true, nullable = false)
    private Integer id;
    
    @Nonnull
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "accountId")
    private Set<Classification> classifications = new HashSet<>();

    
    ... rest fields

}

@Data
@Table(uniqueConstraints = {
        @UniqueConstraint(columnNames = {"accountId", "anotherClassification"})
})
@Entity
@RequiredArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class Classification {

    @Id
    @Nullable
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Integer id;

    @Nonnull
    @Column(nullable = false)
    private Integer accountId;

    @Nonnull
    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private AnotherClassification anotherClassification;
}

现在在前端我编辑分类,例如删除一个条目,所以在请求中我有一个包含一组分类的帐户,这些分类(当然)已经存储在数据库中。发生异常是因为无法再次添加。这是 AccountController 中的 @PutMapping 调用的服务方法:

@Transactional
@Override
public Account updateAccount(Integer id, AccountRequest accountRequest) {
    return accountRepository.findById(id).map(account -> {
        ...
        Set<Classification> classifications = new HashSet<>();
        List<AnotherClassification> requestClassifications = accountRequest.getClassifications();
        requestClassifications.forEach(ac -> {
            Classification classification = new Classification(account.getId(), ac);
            classifications.add(classification);
        });
        account.setClassifications(classifications);
        return accountRepository.save(account);
    }).orElseThrow(() -> idAccountNotFoundException(id));
}

我还尝试构建分类的手动清理(如下所示)并在更新方法的开头调用它,但它没有帮助。条目没有被删除,仍然是同样的错误。

@Override
public void clearClassificationsForAccount(Integer accountId) {
    classificationRepo.deleteAllByAccountId(accountId);
    classificationRepo.flush();
}

进行此类更新的正确方法是什么?

标签: hibernatejpaspring-dataone-to-manyput

解决方案


为什么将分类映射为实体?您可以像这样使用元素集合映射:

@Nonnull
@ElementCollection
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Set<AnotherClassification> classifications = new HashSet<>();

这将创建一个只有两列的表,即您用于唯一约束的列。如果你真的必须使用代理 id,你可以尝试根据你的自然 id 来实现 equals-hashCode,即字段accountIdanotherClassification.


推荐阅读