【问题标题】:Hibernate: duplicate key value violates unique constraint on collectionHibernate:重复的键值违反了集合的唯一约束
【发布时间】:2016-08-21 17:58:53
【问题描述】:

我想导入具有一组专业和一组专业组的配置文件。专业分布在各个专业组中。

定义了以下实体:

@Entity
public class Profile extends BaseEntity<Profile> { // B.E. defines id, creation_time,etc..

    @OneToMany(cascade = CascadeType.ALL)
    private Collection<Profession> professions;

    @OneToMany(cascade = CascadeType.ALL)
    private Collection<ProfessionGroup> professionGroups;

    // .. getters and setters
}

@Entity
private class Profession extends BaseEntity<Profession> {

    @Column(unique = true)
    private String name;

    // getters and setters

}

@Entity
public class ProfessionGroup extends BaseEntity<ProfessionGroup> {

    @Column(unique = true)
    private String name;

    @ManyToOne(cascade = CascadeType.All)
    private Collection<Profession> professions;

    // getters and setters
}

以下代码读取一些序列化为 json 的配置文件并希望将其存储到数据库中:

// ...
Profile p = ...; // read from json using some deserializer
p.getProfessionGroups().forEach(pg -> pg.setProfessions(p.getProfessions());

// ..
ProfileService profileService = ...; // 
profileService.save(profile);

ProfileService 在内部调用 entityManager.persist(...)。 这里的问题是,每当我想将所有职业分配给所有职业组时,我都会得到一个“重复的键值违反唯一约束”。我可以做些什么来安全地存储配置文件,而不会违反唯一键约束。 JPA 显然希望为职业组中的每个条目创建一个新职业。但是,对专业的引用是相同的。调用 merge(...) 没有成功。

【问题讨论】:

    标签: hibernate jpa


    【解决方案1】:

    问题在于级联的定义,以及 JPA 尤其是 hibernate 如何处理新实体实例。

    entitymanager.persist被调用时,它存储和管理实体的状态,而不是你传递给entityManager.persist的实际对象。

    托管实例和传递的参数会有所不同。因此,如果您手动生成 id,使用同一对象调用 entitymanager.persist 两次将导致来自数据库而不是来自 jpa 的 DuplicateKeyException

    为了解决这个问题,您需要持久化并获取对 Profession 实例的托管实体的引用,您可以在 Profile 和 ProfessionGroup 中使用它们,因此:

    Profile profile = loadProfiles();
    List<Profession> managedProfessions = profile
                 .getProfessions()
                 .stream()
                 .map((p) -> entityManager.merge(p)) //Note that we use the returned value, since the returned value is what is actually managed, the passed parameter is not, and will be discarded by the persistent-context
                 .Collect(Collectors.toList());
    profile.setProfessions(managedProfessions);
    profile.getProfessionGroups().forEach((gr)->gr.setProfessions(managedProfessions));
    
    profileService.save(profile);
    

    有了这个,你可能想删除Cascade.ALL,只用Cascade.MERGE替换它。

    【讨论】:

    • 你的答案是正确的,但上下文更广泛一些。
    【解决方案2】:

    有时可以在其他地方找到解决方案:

    实体“职业”和“职业组”之间的关系是错误的。应该是:

    @Entity
    public class ProfessionGroup extends BaseEntity<ProfessionGroup> {
    
        @Column(unique = true)
        private String name;
    
        @ManyToMany(cascade = CascadeType.All)
        private Collection<Profession> professions;
    
       // getters and setters
       // ..
    }
    

    一个职业可以属于一个或多个职业组,因为一个职业组可以有一个或多个职业。解决这个问题,并调整来自@maress 的有价值的答案,解决方案现在看起来像这样:

    Profile profile = mapper.readValue(json, Profile.class);
    
    List<Profession> managedProfessions = profile
            .getProfessions()
            .stream()
            .map(p -> {
                return professionService.update(p);
            })
            .map(Optional::get)
            .collect(Collectors.toList());
    profile.setProfessions(managedProfessions);
    profile.getProfessiongroups().forEach(professionGroup -> {
        professionGroup.setProfessions(managedProfessions);
    });
    
    profileService.save(profile);
    

    小菜一碟。

    【讨论】:

      猜你喜欢
      • 2016-09-27
      • 1970-01-01
      • 2020-05-28
      • 1970-01-01
      • 2019-09-22
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 2022-01-13
      相关资源
      最近更新 更多