1概要

フル機能のORMフレームワークとして、Hibernateは

read



save



update



delete

などのCRUD操作を含む永続オブジェクト(エンティティ)のライフサイクル管理を担当します。

この記事では、Hibernate ** を使ってデータベースからオブジェクトを削除するさまざまな方法を探り、一般的な問題と起こりうる落とし穴について説明します。

JPAを使用し、JPAで標準化されていない機能については、HibernateネイティブAPIのみを使用します。


2オブジェクトを削除するさまざまな方法

以下のシナリオでは、オブジェクトを削除することができます。


  • EntityManager.remove

    を使用する

  • 他のエンティティインスタンスから削除がカスケードされたとき


  • orphanRemoval

    を適用した場合


  • delete

    JPQLステートメントを実行することによって

  • ネイティブクエリを実行することによって

  • ソフト削除テクニックを適用する(ソフト削除エンティティをフィルタリングする)


@ 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

操作が適用された場合、削除されたインスタンスは再永続化されます。よくある間違いは、http://download.oracleのセクション3.2.2で、

PERSIST

操作が削除されたインスタンスに適用されたことを無視することです(通常、フラッシュ時に別のインスタンスからカスケードされるため)。 com/otndocs/jcp/persistence-2__1-fr-eval-spec/index.html[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());

削除された

Bar



Foo

によって参照されている場合、関連付けには

cascade = CascadeType.ALL

のマークが付けられ、削除はスケジュールされていないため、

PERSIST

操作は

Foo

から

Bar

にカスケードされます。これが行われていることを確認するために、

org.hibernate

パッケージのトレースログレベルを有効にし、

un-schedule entity deleting

などのエントリを検索します。

** 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

が削除されます。

他の親インスタンスに関連付けられている可能性のある子インスタンスを削除することになるため、

REMOVE

操作を

@ ManyToMany

関連付けにカスケードすることは、ほとんどの場合バグです。これは、

REMOVE

操作を含むすべての操作がカスケードされることを意味するため、

CascadeType.ALL

にも適用されます。


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

    操作とまったく同じです。結果として、あなたは他のインスタンスが削除されたものを参照しないことを確実にしなければなりません(そうでなければそれらは再永続化されます)。


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はエンティティに配置でき、生成されたSQLクエリに自動的に追加されるSQLフラグメントを含む

@ Where

アノテーションを提供します。その実体のために。

これを示すために、

@ 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チュートリアルによるオブジェクトの削除の実装はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa[Githubで追加]を利用できます。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。