2017-02-08 9 views
1

このシナリオでは、ヘッダーレコードを取得し、その詳細を削除して別の方法で詳細を再作成する必要があります。詳細を更新することはあまりにも面倒です。削除すると、レコードが作成され、Springデータで重複するキー違反が発生するJPA

は、私は基本的に持っている:私はこれを実行し、2、3、20、などを渡すとき、

Header 
================================= 
ID, other columns... 

Detail 
================================= 
ID, HEADER_ID, CUSTOMER_ID 

Customer 
================================= 
ID, other columns... 

Constraint: Details must be unique by HEADER_ID and CUSTOMER_ID so: 

Detail (VALID) 
================================= 
1, 123, 10 
2, 123, 12 

Detail (IN-VALID) 
================================= 
1, 123, 10 
1, 123, 10 

OK:今

@Transactional 
public void create(Integer id, List<Integer> customerIDs) { 

    Header header = headerService.findOne(id); 
    // header is found, has multiple details 

    // Remove the details 
    for(Detail detail : header.getDetails()) { 
     header.getDetails().remove(detail); 
    } 

    // Iterate through list of ID's and create Detail with other objects 
    for(Integer id : customerIDs) { 
     Customer customer = customerService.findOne(id); 

     Detail detail = new Detail(); 
     detail.setCustomer(customer); 

     header.getDetails().add(detail); 
    } 

    headerService.save(header); 
} 

は、データベースは次のような制約があります顧客は、それ以前に存在しない限りすべての Detailレコードを作成します。

別の顧客リストを渡すと、最初にALLの詳細が削除され、次にNEWの詳細のリストが作成されると思います。

しかし、何が起こっているのは、削除が作成前に尊重されていないようです。エラーは重複するキー制約です。重複キーは上記の「有効」シナリオです。

データベースに手動で詳細情報を入力し、CREATE detailsの部分をコメントアウトすると(削除のみを実行すると)、レコードは正常に削除されます。したがって、削除が機能します。作成は機能します。両方とも一緒に働かないことだけです。

私はより多くのコードを提供する必要があります。私はSpring Data JPAを使用しています。

おかげ

UPDATE

私のエンティティは、基本的には次の注釈を付けています

@Entity 
@Table 
public class Header { 
... 
    @OneToMany(mappedBy = "header", orphanRemoval = true, cascade = {CascadeType.ALL}, fetch = FetchType.EAGER) 
    private Set<Detail> Details = new HashSet<>(); 

... 
} 

@Entity 
@Table 
public class Detail { 
... 
    @ManyToOne(optional = false) 
    @JoinColumn(name = "HEADER_ID", referencedColumnName = "ID", nullable = false) 
    private Header header; 
... 
} 

UPDATE 2

@Klaus Groenbaek

実際、私はこれをもともと言及していませんでしたが、私はそのように初めて行いました。また、私はPERSISTを含むと仮定しているCascading.ALLを使用しています。私はすぐに作成していない場合は、削除が動作することを....私は改めてたい...再び

@Transactional 
public void create(Integer id, List<Integer> customerIDs) { 

    Header header = headerService.findOne(id); 

    // Remove the details 
    detailRepository.delete(header.getDetails());  // Does not work 

    // I've also tried this: 
    for(Detail detail : header.getDetails()) { 
     detailRepository.delete(detail); 
    } 


    // Iterate through list of ID's and create Detail with other objects 
    for(Integer id : customerIDs) { 
     Customer customer = customerService.findOne(id); 

     Detail detail = new Detail(); 
     detail.setCustomer(customer); 
     detail.setHeader(header); 

     detailRepository.save(detail) 
    } 
} 

ちょうどテストのために、私は次のように私のコードを更新しましたその後。その直前に削除がない場合、作成は動作します。しかし、データベースから重複したキー制約エラーのために一緒になっていても、どちらも機能しません。

カスケード削除の場合と同じシナリオを試しましたが、

+0

どのように詳細をマッピングしたかによって異なります...単に「.remove(...)」と指定すると、DB内の行が常に削除されるわけではありません – Andremoniy

+0

したがって、エンティティにアノテーションを付ける必要があります – Andremoniy

+0

なぜ、近い票を得ていますか?とにかく... @Andremoniyはい、私はすべてカスケードで私のエンティティに注釈を付けました。私はその情報で質問を更新します。 – cbmeeks

答えて

4

これはかなり長い説明ですが、あなたのコードを見ると、JPAの仕組みに関するいくつかの重要な概念が不足しているように見えます。

最初に、コレクションにエンティティを追加するか、コレクションからエンティティを削除しても、カスケード操作またはorphanRemovalを使用して持続操作が伝播されない限り、データベース内で同じ操作が行われるということは意味しません。

エンティティをデータベースに追加するには、EntityManager.persist()を直接呼び出すか、連続してカスケード接続する必要があります。これは基本的に内部で起こることですJPARepository.save()

エンティティを削除する場合は、EntityManager.remove()を直接またはカスケードするか、JpaRepository.delete()を呼び出して呼び出す必要があります。

管理対象エンティティ(永続コンテキストにロードされているエンティティ)があり、トランザクション内で基本フィールド(非エンティティ、非コレクション)を変更すると、この変更はデータベースに書き込まれます。 persist/saveに電話していなくても、トランザクションはコミットします。永続コンテキストはロードされたすべてのエンティティの内部コピーを保持し、トランザクションがコミットすると内部コピーをループして現在の状態と比較し、基本的なファイル変更によって更新クエリがトリガされます。

新しいエンティティ(A)を別のエンティティ(B)のコレクションに追加したが、Aで永続性を呼び出さなかった場合、Aはデータベースに保存されません。 Bでpersistを呼び出すと、2つのうちの1つが実行されます。持続操作がカスケードされている場合、Aもデータベースに保存されます。永続性がカスケードされていない場合、マネージエンティティはマネージエンティティを参照するため、EclipseLinkでこのエラーが発生するため、「エラーが発生します。カスケードの永続性は理にかなっています。なぜなら、あなたは親エンティティを作成し、同時にその子であるためです。

エンティティAを別のエンティティBのコレクションから削除する場合は、Bを削除していないのでカスケードに頼ることはできません。代わりに、Aで直接removeを呼び出してコレクションから削除する必要がありますEntityManagerで永続化操作が呼び出されていないので、B上では何の効果もありません。 orphanRemovalを使用して削除をトリガーすることもできますが、特にこの機能を使用するときに注意することをお勧めします。特に、永続性操作の仕組みに関する基本的な知識が不足しているようです。

通常、永続化操作とそれに適用する必要があるエンティティについて考えることをお勧めします。私がそれを書いたなら、コードがどのように見えたかは次のとおりです。

@Transactional 
public void create(Integer id, List<Integer> customerIDs) { 

    Header header = headerService.findOne(id); 
    // header is found, has multiple details 

    // Remove the details 
    for(Detail detail : header.getDetails()) { 
     em.remove(detail); 
    } 

    // em.flush(); // In some case you need to flush, see comments below 

    // Iterate through list of ID's and create Detail with other objects 
    for(Integer id : customerIDs) { 
     Customer customer = customerService.findOne(id); 

     Detail detail = new Detail(); 
     detail.setCustomer(customer); 
     detail.setHeader(header); // did this happen inside you service? 
     em.persist(detail); 
    } 
} 

が最初のヘッダーを永続化する理由はありません、それは、管理エンティティであり、トランザクションのコミット時に変更任意の基本的なフィールドが変更になります。ヘッダーはDetailsエンティティの外部キーになります。重要なのはdetail.setHeader(header);em.persist(details)です。これは、すべての外部関係を設定してから新しいDetailsを保持する必要があるためです。 同様に、Headerから既存の詳細を削除し、Headerとは関係がなく、定義関係(外部キー)がDetailsにあるため、永続コンテキストから詳細を削除するのはデータベースから削除されます。 orphanRemovalを使用することもできますが、これはトランザクションごとに追加のロジックが必要です。また、各ペリシティ操作が明示的であればコードを読みやすくなります。アノテーションを読むためにエンティティに戻る必要はありません。

最後に、コード内の永続操作のシーケンスは、データベースに対して実行されたクエリの順序に変換されません。 HibernateとEclipseLinkの両方で新しいエンティティが挿入され、既存のエンティティが削除されます。私の経験では、これが「主キーはすでに存在しています」の最も一般的な理由です。特定の主キーを持つエンティティを削除してから、同じ主キーを持つ新しいエンティティを追加すると、挿入が最初に行われ、キー違反が発生します。これは、現在の永続性状態をデータベースにフラッシュするようにJPAに指示することで修正できます。 em.flush()は、削除クエリをデータベースにプッシュします。したがって、削除したものと同じ主キーを持つ別の行を挿入することができます。

これは多くの情報でした。わからないことがあれば教えてください。

1

まず、header.getDetails().remove(detail);を実行するだけでは、DBに対して何らかの操作を実行しません。 headerService.save(header);にはsession.saveOrUpdate(header)のようなものがあります。

基本的には、それはこれらの操作を実行する順序を知らないHibernateは1回の操作で重複したキーを持つエンティティを削除して作成する必要があるため、論理的な衝突のいくつかの種類ですが、。

私は、少なくともすなわち、このような新しい内容を追加headerService.save(header);を呼び出すことをお勧め:Hibernateに伝えるために

// Remove the details 
    for(Detail detail : header.getDetails()) { 
     header.getDetails().remove(detail); 
    } 

    headerService.save(header); 

    // Iterate through list of ID's and create Detail with other objects 
    for(Integer id : customerIDs) { 
     // .... 
    } 

    headerService.save(header); 

を:はい、私はコレクションから削除されているこのエンティティを削除し、その後、新しいentitesを追加します。

関連する問題