2016-08-21 13 views
0

Professions、Professionsのセット、ProfessionGroupsのセットをインポートします。プロフェッションはプロフェッショングループ全体に分散されています。Hibernate:重複するキー値がコレクションの一意性制約に違反しています

次のエンティティが定義されています。

@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(...)を呼び出すことはそのトリックをしなかった。

答えて

1

ソリューションは、どこか時々見つけることができます:

エンティティの職業」と「professiongroup」の間の関係が間違っています。それがされている必要があります:職業・グループは、1つまたは複数の職業を持っている可能性があるので

@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); 

ケーキの片。

2

カスケードの定義と、JPA、特にhibernateが新しいエンティティインスタンスをどのように処理するかが問題です。

entitymanager.persistが呼び出されると、EntityManager.persistに渡す実際のオブジェクトではなく、エンティティの状態を格納および管理します。管理インスタンスと渡されるパラメータは異なります。したがって、手動でIDを生成する場合、entitymanager.persistを同じオブジェクトで2回呼び出すと、jpaからではなく、データベースからduplicatekeyexcpetionが生成されます。この方法でラウンドを取得するには、あなたはこのように、あなたがプロフィールにし、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`````で置き換えることができます。

+0

あなたの答えは正しいが、文脈はもう少し広まっていた。 – phobos

関連する問題