1. 序章

EntityManagerクラスのgetReference()メソッドは、最初のバージョンからJPA仕様の一部になっています。 ただし、この方法は、基盤となる永続性プロバイダーによって動作が異なるため、一部の開発者を混乱させます。

このチュートリアルでは、 Hibernate EntityManagergetReference()メソッドを使用する方法を説明します。

2. EntityManagerフェッチ操作

まず、主キーでエンティティをフェッチする方法を見ていきます。 EntityManager は、クエリを記述せずに、これを実現するための2つの基本的なメソッドを提供します。

2.1. find()

find()は、エンティティをフェッチする最も一般的な方法です。

Game game = entityManager.find(Game.class, 1L);

このメソッドは、要求時にエンティティを初期化します。

2.2. getReference()

find()メソッドと同様に、 getReference()もエンティティを取得する別の方法です。

Game game = entityManager.getReference(Game.class, 1L);

ただし、返されるオブジェクトは主キーフィールドのみが初期化されたエンティティプロキシです。 他のフィールドは、怠惰に要求しない限り未設定のままです。

次に、これら2つのメソッドがさまざまなシナリオでどのように動作するかを見てみましょう。

3. ユースケースの例

EntityManager のフェッチ操作を示すために、ドメインとして GamePlayer、の2つのモデルを作成し、多くのプレーヤーが同じゲームに参加できるようにします。 。

3.1. ドメインモデル

まず、 Game:というエンティティを定義しましょう。

@Entity
public class Game {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

次に、Playerエンティティを定義します。

@Entity
public class Player {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

3.2. リレーションの構成

プレーヤーからゲームへの@ManyToOneリレーションを構成する必要があります。 それでは、gameプロパティをPlayerエンティティに追加しましょう。

@ManyToOne
private Game game;

4. テストケース

テストメソッドの作成を開始する前に、テストデータを個別に定義することをお勧めします。

entityManager.getTransaction().begin();

entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Game(2L, "Game 2"));
entityManager.persist(new Player(1L,"Player 1"));
entityManager.persist(new Player(2L, "Player 2"));
entityManager.persist(new Player(3L, "Player 3"));

entityManager.getTransaction().commit();

さらに、基盤となるSQLクエリを調べるには、persistence.xmlでHibernateのhibernate.show_sqlプロパティ構成する必要があります。

<property name="hibernate.show_sql" value="true"/>

4.1. エンティティフィールドの更新

まず、 find()メソッドを使用してエンティティを更新する最も一般的な方法を確認します。

それでは、最初に Game エンティティをフェッチしてから、そのnameフィールドを更新するテストメソッドを作成しましょう。

Game game1 = entityManager.find(Game.class, 1L);
game1.setName("Game Updated 1");

entityManager.persist(game1);

テストメソッドを実行すると、実行されたSQLクエリが表示されます。

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: update Game set name=? where id=?

お気づきのとおり、このような場合はSELECTクエリは不要に見えます。 更新操作の前にGameエンティティのフィールドを読み取る必要がないため、UPDATEクエリのみを実行する方法があるかどうか疑問に思っています。

それでは、 getReference()メソッドが同じシナリオでどのように動作するかを見てみましょう。

Game game1 = entityManager.getReference(Game.class, 1L);
game1.setName("Game Updated 2");

entityManager.persist(game1);

驚いたことに、実行中のテストメソッドの結果は同じであり、SELECTクエリが残っていることがわかります

ご覧のとおり、 getReference()を使用してエンティティフィールドを更新すると、HibernateはSELECTクエリを実行します。

したがって、 getReference()メソッドを使用しても、エンティティプロキシのフィールドのセッターを実行した場合に余分なSELECTクエリが回避されることはありません。

4.2. エンティティの削除

削除操作を実行すると、同様のシナリオが発生する可能性があります。

Playerエンティティを削除するための別の2つのテストメソッドを定義しましょう。

Player player2 = entityManager.find(Player.class, 2L);
entityManager.remove(player2);
Player player3 = entityManager.getReference(Player.class, 3L);
entityManager.remove(player3);

これらのテストメソッドを実行すると、同じクエリが表示されます。

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: delete from Player where id=?

同様に、削除操作の場合、結果は同様です。 Playerエンティティのフィールドを読み取らなくても、Hibernateは追加のSELECTクエリも実行します。

したがって、既存のエンティティを削除するときにgetReference()メソッドとfind()メソッドのどちらを選択しても違いはありません。

この時点で、 getReference()はまったく違いがありますか? 実体関連に移り、調べてみましょう。

4.3. 実体関連の更新

別の一般的なユースケースは、エンティティ間の関係を保存する必要がある場合に発生します。

Playergameプロパティを更新するだけで、PlayerGameに参加することを示す別のメソッドを追加しましょう。

Game game1 = entityManager.find(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);

テストを実行すると、もう一度同様の結果が得られ、 find()メソッドを使用した場合でもSELECTクエリを確認できます

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

ここで、この場合のgetReference()メソッドがどのように機能するかを確認するためのもう1つのテストを定義しましょう

Game game2 = entityManager.getReference(Game.class, 2L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game2);

entityManager.persist(player1);

うまくいけば、テストを実行すると、期待される動作が得られます。

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

今回getReference()を使用すると、HibernateはGameエンティティのSELECTクエリを実行しません

したがって、この場合は getReference()を選択することをお勧めします。 これは、プロキシ GameエンティティがPlayerエンティティから関係を作成するのに十分であるためです— Gameエンティティを初期化する必要はありません。

したがって、 getReference()を使用すると、エンティティリレーションを更新するときにデータベースへの不要なラウンドトリップを排除できます。

5. Hibernateの第1レベルのキャッシュ

メソッドfind()とgetReference()の両方がSELECTクエリを実行しない場合があることは、混乱を招く可能性があります。

操作の前に、エンティティが永続コンテキストにすでにロードされている状況を想像してみましょう。

entityManager.getTransaction().begin();
entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Player(1L, "Player 1"));
entityManager.getTransaction().commit();

entityManager.getTransaction().begin();
Game game1 = entityManager.getReference(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);
entityManager.getTransaction().commit();

テストを実行すると、更新クエリのみが実行されたことがわかります。

Hibernate: update Player set game_id=?, name=? where id=?

このような場合、find()またはgetReference()のどちらを使用しても、SELECTクエリが表示されないことに注意してください。 これは、エンティティがHibernateの第1レベルのキャッシュにキャッシュされているためです。

その結果、エンティティがHibernateの第1レベルのキャッシュに保存されると、find()メソッドとgetReference()メソッドの両方が同じように動作し、データベースにヒットしません

6. さまざまなJPA実装

最後に、 getReference()メソッドの動作は、基盤となる永続性プロバイダーに依存することに注意してください。

JPA 2仕様によると、 getReference()メソッドが呼び出されると、永続性プロバイダーはEntityNotFoundExceptionをスローできます。 したがって、他の永続性プロバイダーでは異なる可能性があり、 getReference()を使用するとEntityNotFoundExceptionが発生する可能性があります。

それにもかかわらず、 HibernateはデフォルトでgetReference()の仕様に従わず、可能な場合はデータベースのラウンドトリップを保存します。 したがって、エンティティプロキシがデータベースに存在しない場合でも、エンティティプロキシを取得するときに例外はスローされません。

あるいは、 Hibernateは、JPA仕様に準拠したい人のために意見の分かれる方法を提供する構成プロパティを提供します。

このような場合、hibernate.jpa.compliance.proxyプロパティをtrueに設定することを検討できます。

<property name="hibernate.jpa.compliance.proxy" value="true"/>

この設定では、Hibernateはどのような場合でもエンティティプロキシを初期化します。つまり、 getReference()を使用している場合でも、SELECTクエリを実行します。

7. 結論

このチュートリアルでは、参照プロキシオブジェクトの恩恵を受けることができるいくつかのユースケースを調査し、Hibernateで EntityManagergetReference()メソッドを使用する方法を学びました。

いつものように、このチュートリアルのすべてのコードサンプルとその他のテストケースは、GitHub利用できます。