Hibernateを使用したオブジェクトの削除
1. 概要
フル機能のORMフレームワークとして、Hibernateは、read、save、update、deleteなどのCRUD操作を含む永続オブジェクト(エンティティ)のライフサイクル管理を担当します。
この記事では、さまざまなways in which objects may be deleted from a database using Hibernateについて説明し、発生する可能性のある一般的な問題と落とし穴について説明します。
JPAを使用し、JPAで標準化されていない機能については、ステップバックしてHibernateネイティブAPIのみを使用します。
2. オブジェクトを削除するさまざまな方法
オブジェクトは、次のシナリオで削除される場合があります。
-
EntityManager.removeを使用する
-
削除が他のエンティティインスタンスからカスケードされる場合
-
orphanRemovalが適用された場合
-
deleteJPQLステートメントを実行する
-
ネイティブクエリを実行する
-
ソフト削除手法を適用する(@Where句の条件でソフト削除されたエンティティをフィルタリングする)
記事の残りの部分では、これらのポイントを詳細に見ていきます。
3. エンティティマネージャを使用した削除
EntityManagerを使用した削除は、エンティティインスタンスを削除する最も簡単な方法です。
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
assertThat(foo, notNullValue());
entityManager.remove(foo);
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
この記事の例では、ヘルパーメソッドを使用して、必要に応じて永続コンテキストをフラッシュおよびクリアします。
void flushAndClear() {
entityManager.flush();
entityManager.clear();
}
EntityManager.removeメソッドを呼び出した後、提供されたインスタンスはremoved状態に遷移し、データベースからの関連する削除は次のフラッシュで発生します。
deleted instance is re-persisted if a PERSIST operation is applied to itに注意してください。 よくある間違いは、PERSIST操作が削除されたインスタンスに適用されたことを無視することです(通常、フラッシュ時に別のインスタンスからカスケードされるため)。JPA specificationのセクション3.2.2が原因です。 )sは、そのような場合にそのようなインスタンスを再度永続化することを義務付けています。
FooからBarへの@ManyToOneの関連付けを定義することにより、これを説明します。
@Entity
public class Foo {
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Bar bar;
// other mappings, getters and setters
}
永続コンテキストにもロードされているFooインスタンスによって参照されるBarインスタンスを削除しても、Barインスタンスはデータベースから削除されません。
Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
bar = entityManager.find(Bar.class, bar.getId());
entityManager.remove(bar);
flushAndClear();
bar = entityManager.find(Bar.class, bar.getId());
assertThat(bar, notNullValue());
foo = entityManager.find(Foo.class, foo.getId());
foo.setBar(null);
entityManager.remove(bar);
flushAndClear();
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());
削除されたBarがFooによって参照されている場合、関連付けはcascade = CascadeType.ALLでマークされているため、PERSIST操作はFooからBarにカスケードされます。削除は予定外です。 これが発生していることを確認するために、org.hibernateパッケージのトレースログレベルを有効にして、un-scheduling entity deletionなどのエントリを検索する場合があります。
4. カスケード削除
親が削除されると、削除は子エンティティにカスケードできます。
Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
entityManager.remove(foo);
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());
ここでは、削除がfooからカスケードされるため、barが削除されます。これは、関連付けがすべてのライフサイクル操作をFooからBarにカスケードするように宣言されているためです。
it is almost always a bug to cascade REMOVE operation in a @ManyToMany associationは、他の親インスタンスに関連付けられている可能性のある子インスタンスの削除をトリガーするため、注意してください。 これは、REMOVE操作を含むすべての操作がカスケードされることを意味するため、CascadeType.ALLにも当てはまります。
5. 孤児の除去
orphanRemovalディレクティブは、関連付けられたエンティティインスタンスが親から関連付け解除されたとき、または同等に親が削除されたときに削除されることを宣言します。
このような関連付けをBarからBaz:に定義することでこれを示します
@Entity
public class Bar {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List bazList = new ArrayList<>();
// other mappings, getters and setters
}
次に、Bazインスタンスは、親Barインスタンスのリストから削除されると、自動的に削除されます。
Bar bar = new Bar("bar");
Baz baz = new Baz("baz");
bar.getBazList().add(baz);
entityManager.persist(bar);
flushAndClear();
bar = entityManager.find(Bar.class, bar.getId());
baz = bar.getBazList().get(0);
bar.getBazList().remove(baz);
flushAndClear();
assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());
The semantics of the orphanRemoval operation is completely similar to a REMOVE operation applied directly to the affected child instancesは、REMOVE操作がネストされた子にさらにカスケードされることを意味します。 結果として、削除されたインスタンスを参照する他のインスタンスがないことを確認する必要があります(そうでない場合、それらは再永続化されます)。
6. JPQLステートメントを使用した削除
HibernateはDMLスタイルの削除操作をサポートしています。
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
entityManager.createQuery("delete from Foo where id = :id")
.setParameter("id", foo.getId())
.executeUpdate();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
DML-style JPQL statements affect neither the state nor life cycle of entity instances that are already loaded into the persistence contextに注意することが重要です。したがって、影響を受けるエンティティをロードする前に実行することをお勧めします。
7. ネイティブクエリを使用した削除
Hibernateでサポートされていない、またはデータベースベンダーに固有の何かを実現するために、ネイティブクエリにフォールバックする必要がある場合があります。 ネイティブクエリを使用してデータベース内のデータを削除することもできます。
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
entityManager.createNativeQuery("delete from FOO where ID = :id")
.setParameter("id", foo.getId())
.executeUpdate();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
JPA DMLスタイルのステートメントと同じ推奨事項がネイティブクエリに適用されます。 native queries affect neither the state nor life cycle of entity instances which are loaded into the persistence context prior to execution of the queries。
8. ソフト削除
多くの場合、監査の目的と履歴の保持のために、データベースからデータを削除することは望ましくありません。 このような状況では、ソフト削除と呼ばれる手法を適用できます。 基本的に、行を削除済みとしてマークし、データを取得するときにそれをフィルターで除外します。
ソフト削除可能なエンティティを読み取るすべてのクエリのwhere句で多くの冗長な条件を回避するために、Hibernateは、エンティティに配置でき、次のSQLフラグメントを含む@Whereアノテーションを提供します。そのエンティティに対して生成されたSQLクエリに自動的に追加されます。
これを示すために、@WhereアノテーションとDELETEDという名前の列をFooエンティティに追加します。
@Entity
@Where(clause = "DELETED = 0")
public class Foo {
// other mappings
@Column(name = "DELETED")
private Integer deleted = 0;
// getters and setters
public void setDeleted() {
this.deleted = 1;
}
}
次のテストでは、すべてが期待どおりに機能することを確認します。
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
foo.setDeleted();
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
9. 結論
この記事では、Hibernateでデータを削除するさまざまな方法を検討しました。 基本的な概念といくつかのベストプラクティスを説明しました。 また、Hibernateを使用してソフト削除を簡単に実装できる方法も示しました。
このHibernateチュートリアルによるオブジェクトの削除の実装はover on Githubで利用できます。 これはMavenベースのプロジェクトなので、インポートしてそのまま実行するのは簡単です。