1. 序章

Hibernateは永続データを管理するための便利なフレームワークですが、内部でどのように機能するかを理解するのは難しい場合があります。

このチュートリアルでは、オブジェクトの状態とそれらの間を移動する方法について学習します。 また、デタッチされたエンティティで発生する可能性のある問題とその解決方法についても説明します。

2. Hibernateのセッション

Session インターフェースは、Hibernateとの通信に使用されるメインツールです。 永続オブジェクトの作成、読み取り、更新、削除を可能にするAPIを提供します。 セッションのライフサイクルは単純です。 それを開き、いくつかの操作を実行してから閉じます。

セッション中にオブジェクトを操作すると、オブジェクトはそのセッションにアタッチされます。 行った変更は、終了時に検出されて保存されます。 閉じた後、Hibernateはオブジェクトとセッション間の接続を切断します。

3. オブジェクトの状態

HibernateのSessionのコンテキストでは、オブジェクトは、一時的、永続的、または切り離された3つの可能な状態のいずれかになります。

3.1. 一時的

どのセッションにもアタッチしていないオブジェクトは一時的な状態です。永続化されていないため、データベースに表現がありません。 セッションはそれを認識しないため、自動的に保存されません。

コンストラクターを使用してユーザーオブジェクトを作成し、それがセッションによって管理されていないことを確認しましょう。

Session session = openSession();
UserEntity userEntity = new UserEntity("John");
assertThat(session.contains(userEntity)).isFalse();

3.2. 持続的に

セッションに関連付けたオブジェクトは永続状態です。保存したか、永続コンテキストから読み取ったため、データベース内の行を表します。

オブジェクトを作成してから、persistメソッドを使用してオブジェクトを永続化してみましょう。

Session session = openSession();
UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
assertThat(session.contains(userEntity)).isTrue();

または、saveメソッドを使用することもできます。 違いは、 persist メソッドはオブジェクトを保存するだけであり、saveメソッドは必要に応じてその識別子を追加で生成することです。

3.3. 切り離し

セッションを閉じると、その中のすべてのオブジェクトが切り離されます。 データベース内の行を表しますが、どのセッションでも管理されなくなりました:

session.persist(userEntity);
session.close();
assertThat(session.isOpen()).isFalse();
assertThatThrownBy(() -> session.contains(userEntity));

次に、一時的なエンティティと切り離されたエンティティを保存する方法を学習します。

4. エンティティの保存と再接続

4.1. 一時的なエンティティの保存

新しいエンティティを作成してデータベースに保存しましょう。 最初にオブジェクトを作成するとき、オブジェクトは一時的な状態になります。

新しいエンティティをpersistするには、persistメソッドを使用します。

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);

次に、最初のオブジェクトと同じ識別子を持つ別のオブジェクトを作成します。 この2番目のオブジェクトは、セッションによってまだ管理されていないため一時的ですが、persistメソッドを使用して永続化することはできません。 これはすでにデータベースに表示されているため、永続層のコンテキストではそれほど新しいものではありません。

代わりに、マージメソッドを使用してデータベースを更新し、オブジェクトを永続的にします

UserEntity onceAgainJohn = new UserEntity("John");
session.merge(onceAgainJohn);

4.2. 切り離されたエンティティの保存

前のセッションを閉じると、オブジェクトは切り離された状態になります。前の例と同様に、オブジェクトはデータベースに表示されますが、現在セッションによって管理されていません。 ]。 merge メソッドを使用して、それらを再び永続化できます。

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
session.close();
session.merge(userEntity);

5. ネストされたエンティティ

ネストされたエンティティを検討すると、事態はさらに複雑になります。 ユーザーエンティティがマネージャーに関する情報も保存するとします。

public class UserEntity {
    @Id
    private String name;

    @ManyToOne
    private UserEntity manager;
}

このエンティティを保存するときは、エンティティ自体の状態だけでなく、ネストされたエンティティの状態についても考慮する必要があります。 永続的なユーザーエンティティを作成してから、そのマネージャーを設定しましょう。

UserEntity userEntity = new UserEntity("John");
session.persist(userEntity);
UserEntity manager = new UserEntity("Adam");
userEntity.setManager(manager);

今すぐ更新しようとすると、例外が発生します。

assertThatThrownBy(() -> {
            session.saveOrUpdate(userEntity);
            transaction.commit();
});
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.baeldung.states.UserEntity.manager -> com.baeldung.states.UserEntity

これは、Hibernateが一時的なネストされたエンティティをどう処理するかを知らないために発生しています。

5.1. ネストされたエンティティの永続化

この問題を解決する1つの方法は、ネストされたエンティティを明示的に永続化することです。

UserEntity manager = new UserEntity("Adam");
session.persist(manager);
userEntity.setManager(manager);

次に、トランザクションをコミットした後、正しく保存されたエンティティを取得できるようになります。

transaction.commit();
session.close();

Session otherSession = openSession();
UserEntity savedUser = otherSession.get(UserEntity.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

5.2. カスケード操作

エンティティクラスでリレーションシップのcascadeプロパティを正しく構成すると、一時的なネストされたエンティティを自動的に永続化できます。

@ManyToOne(cascade = CascadeType.PERSIST)
private UserEntity manager;

オブジェクトを永続化すると、その操作はネストされたすべてのエンティティにカスケードされます。

UserEntityWithCascade userEntity = new UserEntityWithCascade("John");
session.persist(userEntity);
UserEntityWithCascade manager = new UserEntityWithCascade("Adam");

userEntity.setManager(manager); // add transient manager to persistent user
session.saveOrUpdate(userEntity);
transaction.commit();
session.close();

Session otherSession = openSession();
UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John");
assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

6. 概要

このチュートリアルでは、オブジェクトの状態に関してHibernate Sessionがどのように機能するかを詳しく見てきました。 次に、それが引き起こす可能性のあるいくつかの問題とそれらを解決する方法を調べました。

いつものように、ソースコードはGitHubから入手できます。