web-dev-qa-db-ja.com

Hibernate-@ElementCollection-奇妙な削除/挿入動作

@Entity
public class Person {

    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    private List<Location> locations;

    [...]

}

@Embeddable
public class Location {

    [...]

}

次のクラス構造を考えると、新しい場所をPersonの場所のリストに追加しようとすると、常に次のSQLクエリが発生します。

DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson

そして

A lotsa' inserts into the PERSON_LOCATIONS table

Hibernate(3.5.x/JPA 2)は、指定されたPersonに関連するすべてのレコードを削除し、以前のすべてのレコードと新しいレコードを再挿入します。

Locationのequals/hashcodeメソッドで問題を解決できると思いましたが、何も変わりませんでした。

ヒントは大歓迎です!

39
nihilist84

この問題は、JPA wikibookのElementCollectionに関するページで何らかの形で説明されています。

CollectionTableのプライマリキー

JPA 2.0仕様では、IdEmbeddableを定義する方法が提供されていません。 ただし、ElementCollectionマッピングの要素を削除または更新するには、通常、一意のキーが必要です。そうしないと、更新のたびにJPAプロバイダーはCollectionTableEntityからすべてを削除してから、値を挿入し直す必要があります。したがって、JPAプロバイダは、Embeddable内のすべてのフィールドの組み合わせが、外部キー(JoinColunm(s))と組み合わせて一意であると想定するでしょう。ただし、これは非効率的であるか、Embeddableが大きいか複雑な場合には実行不可能な場合があります。

そして、これはまさに(太字の部分)ここで起こることです(Hibernateはコレクションテーブルのプライマリキーを生成せず、コレクションのどの要素が変更されたかを検出する方法がありません)テーブルから古いコンテンツを削除して新しいコンテンツを挿入します)。

ただし、if@OrderColumn(リストの永続的な順序を維持するために使用される列を指定する-Listを使用しているので理にかなっています)、Hibernateはプライマリキーを作成しますorder列と結合列で構成されます)を削除せずにコレクションテーブルを更新できますコンテンツ全体。

次のようなもの(デフォルトの列名を使用する場合):

@Entity
public class Person {
    ...
    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    @OrderColumn
    private List<Location> locations;
    ...
}

参照資料

58
Pascal Thivent

Pascalの答えに加えて、 少なくとも1つの列をNOT NULLとして設定

@Embeddable
public class Location {

    @Column(name = "path", nullable = false)
    private String path;

    @Column(name = "parent", nullable = false)
    private String parent;

    public Location() {
    }

    public Location(String path, String parent) {
        this.path = path;
        this.parent= parent;
    }

    public String getPath() {
        return path;
    }

    public String getParent() {
        return parent;
    }
}

この要件は AbstractPersistentCollection に記載されています。

HHH-7072などの状況の回避策。コレクション要素が、null許容プロパティのみで構成されるコンポーネントである場合、現在、コレクション全体を強制的に再作成する必要があります。詳細については、AbstractCollectionPersisterコンストラクターのhasNotNullableColumnsの使用を参照してください。行ごとに削除するには、現在の「WHERE COL」ではなく、「WHERE(COL =?OR(COL is null AND?is null))」のようなSQLが必要です。 =? "(ほとんどのDBでnullに失敗します。パラメーターは2回バインドする必要があることに注意してください。ORM5+のASTレクリエーションを強制することは理想的ではありませんが、実際にはORM 4の他のオプションではありません。

7
Vlad Mihalcea

ElementCollectionタイプとして定義しているエンティティには、equalsメソッドまたはhashcodeメソッドが定義されておらず、null許容フィールドがあります。エンティティタイプにそれらを(@lombokを介して)提供し、休止状態(v 5.2.14)でコレクションがダーティであるかどうかを識別できるようにしました。

さらに、注釈@Transaction(readonly = true)でマークされたサービスメソッド内にいたため、このエラーが発生しました。 hibernateは関連する要素のコレクションをクリアして再度挿入しようとするため、フラッシュするとトランザクションが失敗し、トレースが非常に困難なメッセージで問題が発生していました。

HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]

エラーが発生したエンティティモデルの例を次に示します

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

public class Entity2 {
  private UUID someUUID;
}

これに変更する

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

@EqualsAndHashCode
public class Entity2 {
  @Column(nullable = false)
  private UUID someUUID;
}

問題を修正しました。幸運を。

2
Mustafakidd

私は同じ問題を抱えていましたが、列挙型のリストをマップしたかった:List<EnumType>

私はこれを次のように動作させました:

@ElementCollection
@CollectionTable(
        name = "enum_table",
        joinColumns = @JoinColumn(name = "some_id")
)
@OrderColumn
@Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();

public void setEnumList(List<EnumType> newEnumList) {
    this.enumTypeList.clear();
    this.enumTypeList.addAll(newEnumList);
}

私の問題は、Listオブジェクトは常にデフォルトのセッターを使用して置き換えられ、したがって、Hibernateはそれを完全に「新しい」オブジェクトとして扱いましたが、列挙は変更されませんでした。

1
tomasulo