Hibernate:重复键值违反了集合

时间:2016-08-21 17:58:53

标签: hibernate jpa

我想导入有一组专业的个人资料,以及一组专业小组。专业分布在ProfessionGroups。

以下实体已定义:

@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(...)没有做到这一点。

2 个答案:

答案 0 :(得分:2)

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

当调用entitymanager.persist时,它会存储和管理实体的状态,但不会传递和传递给EntityManager.persist的实际对象。托管实例和传递的参数将不同。因此,如果您手动生成ID,则使用相同对象调用entitymanager.persist两次将导致数据库中的duplicatekeyexcpetion,而不是来自jpa。为了解决这个问题,你需要坚持并引用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````替换它。

答案 1 :(得分:1)

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

实体与职业之间的关系&#39;和&#39; professiongroup&#39;是错的。应该是:

@Entity
public class ProfessionGroup extends BaseEntity<ProfessionGroup> {

    @Column(unique = true)
    private String name;

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

   // getters and setters
   // ..
}

专业可以在一个或多个专业组中,因为专业组可以有一个或多个职业。解决了这个问题后,解决方案现在看起来像这样:

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);
一块蛋糕。