1. 概要

フル機能のORMフレームワークとして、Hibernateは、 read save update 削除

この記事では、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 状態に遷移し、データベースからの関連する削除が次のフラッシュで発生します。

削除されたインスタンスは、PERSIST操作が適用された場合に再永続化されることに注意してください。 よくある間違いは、 PERSIST 操作が削除されたインスタンスに適用されたことを無視することです(通常、フラッシュ時に別のインスタンスからカスケードされるため)。セクション 3.2.2 [X207X JPA仕様の]は、そのような場合にそのようなインスタンスを再度永続化することを義務付けています。

これを、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());

削除されたBarFooによって参照されている場合、PERSIST操作はFooからBarにカスケードされます。関連付けはcascade= CascadeType.ALL でマークされ、削除はスケジュールされていません。 これが発生していることを確認するために、 org.hibernate パッケージのトレースログレベルを有効にして、スケジュール解除エンティティの削除などのエントリを検索する場合があります。

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

ここで、 bar は、 foo からカスケードされるため削除されます。これは、関連付けがFooからBarへのすべてのライフサイクル操作をカスケードするように宣言されているためです。 。

@ManyToManyアソシエーションでREMOVE操作をカスケードすると、他の親インスタンスに関連付けられている可能性のある子インスタンスの削除がトリガーされるため、ほとんどの場合バグであることに注意してください。 これは、 CascadeType.ALL にも当てはまります。これは、REMOVE操作を含むすべての操作がカスケードされることを意味します。

5. 孤児の除去

orphanRemoval ディレクティブは、関連付けられたエンティティインスタンスが親から関連付け解除されたとき、または同等に親が削除されたときに削除されることを宣言します。

BarからBaz:へのそのような関連付けを定義することによってこれを示します

@Entity
public class Bar {
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Baz> 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());

orphanRemoval操作のセマンティクスは、影響を受ける子インスタンスに直接適用されるREMOVE操作と完全に似ています。つまり、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スタイルのJPQLステートメントは、永続コンテキストに既にロードされているエンティティインスタンスの状態にもライフサイクルにも影響しないため、影響を受けるエンティティをロードする前に実行することをお勧めします。 。

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スタイルのステートメントの場合と同じ推奨事項が、ネイティブクエリに適用されます。 ネイティブクエリは、クエリの実行前に永続コンテキストにロードされるエンティティインスタンスの状態にもライフサイクルにも影響しません

8. ソフト削除

多くの場合、監査目的と履歴の保持のために、データベースからデータを削除することは望ましくありません。 このような状況では、ソフト削除と呼ばれる手法を適用する場合があります。 基本的に、行を削除済みとしてマークし、データを取得するときにその行を除外します。

ソフト削除可能なエンティティを読み取るすべてのクエリのwhere句で多くの冗長な条件を回避するために、Hibernateはエンティティに配置できる@Whereアノテーションを提供します。そのエンティティ用に生成されたSQLクエリに自動的に追加される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チュートリアルを使用したオブジェクトの削除の実装は、Github利用できます。 これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。