JPA が重複エントリを報告する、新しいが同一のエンティティを永続化する
-
20-09-2019 - |
質問
JPA プロジェクトが MySQL データベースに接続されており、エンティティ オブジェクトが 2 列の制約を持つテーブルにマップされています。あれは:
@Entity
@Table(name = "my_entity")
class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@Column(name = "myField1")
private String myField1;
@Basic(optional = false)
@Column(name = "myField2")
private int myField2;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "myEntity")
private Set<OtherEntity> otherEntitySet;
}
データベースでは、my_entity テーブルに (myField1、myField2) に対する一意の制約があります。問題は、EntityManager.remove(entity) で既存のエンティティを削除し、EntityManager.persist(entity) で新しいエンティティを追加すると、データベースが重複行に関するエラーをスローすることです。
例えば:
entityManager.getTransaction().begin();
MyEntity entity1 = new MyEntity();
entity1.setMyField1("Foo");
entity1.setMyField2(500);
entityManager.persist(entity1);
entityManager.getTransaction().commit();
entityManager.getTransaction().begin();
entityManager.remove(entity1);
MyEntity entity2 = new MyEntity();
entity2.setMyField1("Foo");
entity2.setMyField2(500);
entityManager.persist(entity2);
entityManager.getTransaction().commit();
これにより、これが重複エントリであることを示す MySQLIntegrityConstraintViolationException が発生します。古いエントリを削除する前に新しいエントリを追加しようとしているためだと思います。その順序を維持する方法はありますか?または、JPA を使用してこの状況を防ぐ方法はありますか?これは一般的なユースケースではありませんが、エンティティを削除して関連するデータをすべて削除して最初からやり直し、その後、簡単なフィールドを再作成しようとして、その後データが削除されていないことが判明するユーザーについて懸念しています。
hashCodeとequalsの実装は次のとおりです。
public int hashCode() {
int hash = 0;
hash += (getMyField1().hashCode() + getMyField2());
return hash;
}
public boolean equals(Object object) {
if (!(object instanceof MyEntity)) {
return false;
}
MyEntity other = (MyEntity) other;
return (getMyField2() == other.getMyField2()) &&
(getMyField1().equals(other.getMyField1()));
}
解決
JPAには操作順序を指定する標準的な方法はないと思うので、ステートメントがどの順序で実行されるかは保証されません。理想的には、JPA 実装はこの状況を検出し、挿入前に削除を実行するのに十分賢いものですが、それは頻繁に失敗する領域です。あるいは、データベースが遅延制約チェックをサポートしている場合 (たとえば、Oracle はサポートしているが MySQL はサポートしていない)、データベースはコミット時まで待機して一意制約違反の例外を与えることでこれを処理します。
したがって、解決策の 1 つは、remove(entity1) の呼び出し後に追加のコミットを実行することです。もう 1 つの方法は、entity2 を作成する前に、まずそれがデータベースに存在するかどうかを確認し、存在する場合はそれを使用することです。これらのオプションはどちらもやや面倒な場合があり、すべてのワークフローに適しているわけではありません。現在の JPA 実装のドキュメントを詳しく調べて、役立つ拡張機能が提供されているかどうかを確認してください。
他のヒント
私はUCと間違った順序で生じる挿入/欠失を含む、同じ問題を抱えていました。私はUCの列にマッチした、私のエンティティの複合キーを作成する必要がありました。その後、欠失および挿入は、正しい順序で発生しました。この記事、および@EmbeddedIdの追加についてのコメントを参照してください。