Guida rapida a EntityManager # getReference ()

1. Introduzione

Il metodo getReference () della classe EntityManager fa parte della specifica JPA sin dalla prima versione. Tuttavia, questo metodo confonde alcuni sviluppatori perché il suo comportamento varia a seconda del provider di persistenza sottostante.

In questo tutorial, spiegheremo come utilizzare il metodo getReference () in Hibernate EntityManager .

2. Operazioni di recupero di EntityManager

Prima di tutto, daremo un'occhiata a come possiamo recuperare le entità dalle loro chiavi primarie. Senza scrivere alcuna query, EntityManager ci fornisce due metodi di base per raggiungere questo obiettivo.

2.1. trova()

find () è il metodo più comune per recuperare entità:

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

Questo metodo inizializza l'entità quando lo richiediamo.

2.2. getReference ()

Simile al metodo find () , getReference () è anche un altro modo per recuperare entità:

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

Tuttavia, l'oggetto restituito è un proxy di entità che ha inizializzato solo il campo della chiave primaria . Gli altri campi rimangono non impostati a meno che non li richiediamo pigramente.

Successivamente, vediamo come si comportano questi due metodi in vari scenari.

3. Un caso d'uso di esempio

Per dimostrare le operazioni di recupero di EntityManager , creeremo due modelli, Gioco e Giocatore, come dominio in cui molti giocatori possono essere coinvolti nello stesso gioco.

3.1. Modello di dominio

Per prima cosa, definiamo un'entità chiamata Game:

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

Successivamente, definiamo la nostra entità Player :

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

3.2. Configurazione delle relazioni

Dobbiamo configurare una relazione @ManyToOne da Player a Game . Quindi, aggiungiamo una proprietà di gioco alla nostra entità Player :

@ManyToOne private Game game; 

4. Casi di test

Prima di iniziare a scrivere i nostri metodi di test, è una buona pratica definire i nostri dati di test separatamente:

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

Inoltre, per esaminare sottostante query SQL, dovremmo configurare di Hibernate hibernate.show_sql proprietà nei nostri persistence.xml :

4.1. Aggiornamento dei campi entità

Innanzitutto, controlleremo il modo più comune per aggiornare un'entità utilizzando il metodo find () .

Quindi, scriviamo un metodo di test per recuperare prima l'entità del gioco , quindi aggiorna semplicemente il suo campo del nome :

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

L'esecuzione del metodo di test ci mostra le query SQL eseguite:

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

Come abbiamo notato, la query SELECT non sembra necessaria in questo caso . Poiché non è necessario leggere alcun campo dell'entità Gioco prima della nostra operazione di aggiornamento, ci chiediamo se esiste un modo per eseguire solo la query UPDATE .

Quindi, vediamo come si comporta il metodo getReference () nello stesso scenario:

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

Sorprendentemente, il risultato del metodo di test in esecuzione è sempre lo stesso e vediamo che la query SELECT rimane .

Come possiamo vedere, Hibernate esegue una query SELECT quando usiamo getReference () per aggiornare un campo entità.

Pertanto, l' utilizzo del metodo getReference () non evita la query SELECT aggiuntiva se eseguiamo qualsiasi setter dei campi del proxy di entità.

4.2. Eliminazione di entità

Uno scenario simile può verificarsi quando eseguiamo operazioni di eliminazione.

Definiamo altri due metodi di test per eliminare un'entità Player :

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

L'esecuzione di questi metodi di test ci mostra le stesse query:

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

Allo stesso modo, per le operazioni di cancellazione, il risultato è simile. Anche se non leggiamo alcun campo dell'entità Player , Hibernate esegue anche una query SELECT aggiuntiva .

Quindi, non c'è differenza se scegliamo il metodo getReference () o find () quando cancelliamo un'entità esistente.

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.