Guide rapide d'EntityManager # getReference ()

1. Introduction

La méthode getReference () de la classe EntityManager fait partie de la spécification JPA depuis la première version. Cependant, cette méthode confond certains développeurs car son comportement varie en fonction du fournisseur de persistance sous-jacent.

Dans ce tutoriel, nous allons expliquer comment utiliser la méthode getReference () dans Hibernate EntityManager .

2. Opérations d' extraction d' EntityManager

Tout d'abord, nous allons voir comment nous pouvons récupérer des entités par leurs clés primaires. Sans écrire aucune requête, EntityManager nous fournit deux méthodes de base pour y parvenir.

2.1. trouver()

find () est la méthode la plus courante pour récupérer des entités:

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

Cette méthode initialise l'entité lorsque nous la demandons.

2.2. getReference ()

Semblable à la méthode find () , getReference () est également un autre moyen de récupérer des entités:

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

Cependant, l'objet renvoyé est un proxy d'entité dont seul le champ de clé primaire est initialisé . Les autres champs restent inchangés à moins que nous ne les demandions paresseusement.

Ensuite, voyons comment ces deux méthodes se comportent dans divers scénarios.

3. Un exemple de cas d'utilisation

Afin de démontrer les opérations de récupération d' EntityManager , nous allons créer deux modèles, Game et Player, comme notre domaine dans lequel de nombreux joueurs peuvent être impliqués dans le même jeu.

3.1. Modèle de domaine

Tout d'abord, définissons une entité appelée Game:

@Entity public class Game { @Id private Long id; private String name; // standard constructors, getters, setters } 

Ensuite, nous définissons notre entité Player :

@Entity public class Player { @Id private Long id; private String name; // standard constructors, getters, setters } 

3.2. Configurer les relations

Nous devons configurer une relation @ManyToOne du joueur au jeu . Alors, ajoutons une propriété de jeu à notre entité Player :

@ManyToOne private Game game; 

4. Cas de test

Avant de commencer à écrire nos méthodes de test, il est recommandé de définir nos données de test séparément:

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

De plus, pour examiner les requêtes SQL sous-jacentes, nous devons configurer la propriété hibernate.show_sql d'Hibernate dans notre persistence.xml :

4.1. Mise à jour des champs d'entité

Tout d'abord, nous allons vérifier la manière la plus courante de mettre à jour une entité en utilisant la méthode find () .

Alors, écrivons une méthode de test pour récupérer d'abord l' entité Game , puis mettons simplement à jour son champ de nom :

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

L'exécution de la méthode de test nous montre les requêtes SQL exécutées:

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=? 

Comme nous le remarquons, la requête SELECT semble inutile dans un tel cas . Comme nous n'avons pas besoin de lire un champ de l' entité Game avant notre opération de mise à jour, nous nous demandons s'il existe un moyen d'exécuter uniquement la requête UPDATE .

Voyons donc comment la méthode getReference () se comporte dans le même scénario:

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

Étonnamment, le résultat de la méthode de test en cours d'exécution est toujours le même et nous voyons que la requête SELECT reste .

Comme nous pouvons le voir, Hibernate exécute une requête SELECT lorsque nous utilisons getReference () pour mettre à jour un champ d'entité.

Par conséquent, l' utilisation de la méthode getReference () n'évite pas la requête SELECT supplémentaire si nous exécutons un setter des champs du proxy d'entité.

4.2. Suppression d'entités

Un scénario similaire peut se produire lorsque nous exécutons des opérations de suppression.

Définissons deux autres méthodes de test pour supprimer une entité Player :

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

L'exécution de ces méthodes de test nous montre les mêmes requêtes:

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=? 

De même, pour les opérations de suppression, le résultat est similaire. Même si nous ne lisons aucun champ de l' entité Player , Hibernate exécute également une requête SELECT supplémentaire .

Par conséquent, il n'y a aucune différence que nous choisissions la méthode getReference () ou find () lorsque nous supprimons une entité existante.

At this point, we're wondering, does getReference() make any difference at all then? Let's move on to entity relations and find out.

4.3. Updating Entity Relations

Another common use-case shows up when we need to save relations between our entities.

Let's add another method to demonstrate a Player‘s participation in a Game by simply updating the Player‘s game property:

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

Running the test gives us a similar result one more time and we can still see the SELECT queries when using the find() method:

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=? 

Now, let's define one more test to see how the getReference() method works in this case:

Game game2 = entityManager.getReference(Game.class, 2L); Player player1 = entityManager.find(Player.class, 1L); player1.setGame(game2); entityManager.persist(player1); 

Hopefully, running the test gives us the expected behavior:

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=? 

And we see, Hibernate doesn't execute a SELECT query for the Game entity when we use getReference() this time.

So, it looks like a good practice to choose getReference() in this case. That's because a proxy Game entity is enough to create the relationship from the Player entity — the Game entity does not have to be initialized.

Consequently, using getReference() can eliminate needless roundtrips to our database when we update entity relations.

5. Hibernate First-Level Cache

It can be confusing sometimes that both methods find() and getReference() may not execute any SELECT queries in some cases.

Let's imagine a situation that our entities are already loaded in the persistence context before our operation:

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

Running the test shows that only the update query executed:

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

In such a case, we should notice that we don't see any SELECT queries, whether we use find() or getReference(). This is because our entities are cached in Hibernate's first-level cache.

As a result, when our entities are stored in Hibernate's first-level cache, then both find() and getReference() methods act identical and do not hit our database.

6. Different JPA Implementations

As a final reminder, we should be aware that the behavior of the getReference() method depends on the underlying persistence provider.

According to the JPA 2 Specification, the persistence provider is allowed to throw the EntityNotFoundException when the getReference() method is called. Thus, it may be different for other persistence providers and we may encounter EntityNotFoundException when we use getReference().

Nevertheless, Hibernate doesn't follow the specification for getReference() by default to save a database roundtrip when possible. Accordingly, it doesn't throw an exception when we retrieve entity proxies, even if they don't exist in the database.

Alternatively, Hibernate provides a configuration property to offer an opinionated way for those who want to follow the JPA specification.

In such a case, we may consider setting the hibernate.jpa.compliance.proxy property to true:

With this setting, Hibernate initializes the entity proxy in any case, which means it executes a SELECT query even when we use getReference().

7. Conclusion

In this tutorial, we explored some use-cases that can benefit from the reference proxy objects and learned how to use EntityManager‘s getReference() method in Hibernate.

Like always, all the code samples and more test cases for this tutorial are available over on GitHub.