Contexte de persistance JPA / Hibernate

1. Vue d'ensemble

Les fournisseurs de persistance comme Hibernate utilisent le contexte de persistance pour gérer le cycle de vie des entités dans une application.

Dans ce tutoriel, nous commencerons par l'introduction du contexte de persistance, puis nous verrons pourquoi c'est important. Enfin, nous verrons la différence entre le contexte de persistance à portée de transaction et le contexte de persistance à portée étendue avec des exemples.

2. Contexte de persistance

Jetons un coup d'œil à la définition officielle du contexte de persistance:

Une instance EntityManager est associée à un contexte de persistance. Un contexte de persistance est un ensemble d'instances d'entité dans lequel, pour toute identité d'entité persistante, il existe une instance d'entité unique. Dans le contexte de persistance, les instances d'entité et leur cycle de vie sont gérés. L'API EntityManager est utilisée pour créer et supprimer des instances d'entités persistantes, pour rechercher des entités par leur clé primaire et pour interroger des entités.

La déclaration ci-dessus peut sembler un peu complexe pour le moment, mais elle aura tout son sens à mesure que nous avancerons. Le contexte de persistance est le cache de premier niveau où toutes les entités sont extraites de la base de données ou enregistrées dans la base de données . Il se situe entre notre application et le stockage persistant.

Le contexte de persistance assure le suivi de toutes les modifications apportées à une entité gérée. Si quelque chose change pendant une transaction, l'entité est marquée comme sale. Une fois la transaction terminée, ces modifications sont vidées dans le stockage persistant.

L' EntityManager est l'interface qui nous permet d'interagir avec le contexte de persistance. Chaque fois que nous utilisons EntityManager , nous interagissons en fait avec le contexte de persistance .

Si chaque modification apportée à l'entité appelle un stockage persistant, nous pouvons imaginer combien d'appels seront effectués. Cela entraînera un impact sur les performances car les appels de stockage persistant sont coûteux.

3. Type de contexte de persistance

Les contextes de persistance sont disponibles en deux types:

  • Contexte de persistance à l'échelle de la transaction
  • Contexte de persistance à portée étendue

Jetons un coup d'œil à chacun.

3.1 Contexte de persistance à l'échelle de la transaction

Le contexte de persistance de transaction est lié à la transaction. Dès que la transaction se termine, les entités présentes dans le contexte de persistance seront vidées dans le stockage persistant.

Lorsque nous effectuons une opération dans la transaction, EntityManager vérifie un contexte de persistance . S'il en existe un, il sera utilisé. Sinon, cela créera un contexte de persistance.

Le type de contexte de persistance par défaut est PersistenceContextType.TRANSACTION . Pour dire à EntityManager d'utiliser le contexte de persistance de transaction, nous l' annotons simplement avec @PersistenceContext :

@PersistenceContext private EntityManager entityManager;

3.2 Contexte de persistance à portée étendue

Un contexte de persistance étendu peut s'étendre sur plusieurs transactions. Nous pouvons conserver l'entité sans la transaction, mais ne pouvons pas la vider sans transaction.

Pour indiquer à EntityManager d'utiliser un contexte de persistance à portée étendue, nous devons appliquer l' attribut type de @PersistenceContext :

@PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager;

Dans le bean session sans état, le contexte de persistance étendue dans un composant ignore complètement le contexte de persistance d'un autre composant . Cela est vrai même si les deux sont dans la même transaction.

Disons que nous persistons une entité dans une méthode du composant A , qui s'exécute dans une transaction. Nous appelons ensuite une méthode de la composante B . Dans le composant de B méthode contexte de persistance, nous ne trouverons pas l'entité que nous précédemment dans persévéré la méthode de la composante A .

4. Exemple de contexte de persistance

Maintenant que nous en savons suffisamment sur le contexte de persistance, il est temps de plonger dans un exemple. Nous allons faire différents cas d'utilisation avec un contexte de persistance de transaction et un contexte de persistance étendu.

Tout d'abord, créons notre classe de service, TransctionPersistenceContextUserService :

@Component public class TransctionPersistenceContextUserService { @PersistenceContext private EntityManager entityManager; @Transactional public User insertWithTransaction(User user) { entityManager.persist(user); return user; } public User insertWithoutTransaction(User user) { entityManager.persist(user); return user; } public User find(long id) { return entityManager.find(User.class, id); } }

La classe suivante, ExtendedPersistenceContextUserService , est très similaire à celle ci-dessus, à l'exception de l' annotation @PersistenceContext . Cette fois, nous passons PersistenceContextType.EXTENDED dans le paramètre type de son annotation @PersistenceContext :

@Component public class ExtendedPersistenceContextUserService { @PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager; // Remaining code same as above }

5. Cas de test

Maintenant que nos classes de service sont configurées, il est temps de créer différents cas d'utilisation avec un contexte de persistance des transactions et un contexte de persistance étendu.

5.1 Test du contexte de persistance des transactions

Faisons persister une entité User à l' aide d'un contexte de persistance à portée de transaction. L'entité sera enregistrée dans un stockage persistant. Nous vérifions ensuite en effectuant un appel de recherche à l'aide de EntityManager de notre contexte de persistance étendue :

User user = new User(121L, "Devender", "admin"); transctionPersistenceContext.insertWithTransaction(user); User userFromTransctionPersistenceContext = transctionPersistenceContext .find(user.getId()); assertNotNull(userFromTransctionPersistenceContext); User userFromExtendedPersistenceContext = extendedPersistenceContext .find(user.getId()); assertNotNull(userFromExtendedPersistenceContext);

When we try to insert a User entity without a transaction, then TransactionRequiredException will be thrown:

@Test(expected = TransactionRequiredException.class) public void testThatUserSaveWithoutTransactionThrowException() { User user = new User(122L, "Devender", "admin"); transctionPersistenceContext.insertWithoutTransaction(user); }

5.2 Testing Extended Persistence Context

Next, let's persist the user with an extended persistence context and without a transaction. The User entity will be saved in the persistence context (cache) but not in persistent storage:

User user = new User(123L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user); User userFromExtendedPersistenceContext = extendedPersistenceContext .find(user.getId()); assertNotNull(userFromExtendedPersistenceContext); User userFromTransctionPersistenceContext = transctionPersistenceContext .find(user.getId()); assertNull(userFromTransctionPersistenceContext);

In the persistence context for any persistent entity identity, there will be a unique entity instance. If we try to persist another entity with the same identifier:

@Test(expected = EntityExistsException.class) public void testThatPersistUserWithSameIdentifierThrowException() { User user1 = new User(126L, "Devender", "admin"); User user2 = new User(126L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user1); extendedPersistenceContext.insertWithoutTransaction(user2); }

We'll see EntityExistsException:

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session

Extended persistence context within a transaction saves the entity in persistent storage at the end of the transaction:

User user = new User(127L, "Devender", "admin"); extendedPersistenceContext.insertWithTransaction(user); User userFromDB = transctionPersistenceContext.find(user.getId()); assertNotNull(userFromDB);

Le contexte de persistance étendue vide les entités mises en cache dans le stockage persistant lorsqu'il est utilisé dans la transaction . Tout d'abord, nous conservons l'entité sans transaction. Ensuite, nous persistons une autre entité dans la transaction:

User user1 = new User(124L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user1); User user2 = new User(125L, "Devender", "admin"); extendedPersistenceContext.insertWithTransaction(user2); User user1FromTransctionPersistenceContext = transctionPersistenceContext .find(user1.getId()); assertNotNull(user1FromTransctionPersistenceContext); User user2FromTransctionPersistenceContext = transctionPersistenceContext .find(user2.getId()); assertNotNull(user2FromTransctionPersistenceContext);

6. Conclusion

Dans ce didacticiel, nous avons acquis une bonne compréhension du contexte de persistance.

Tout d'abord, nous avons examiné le contexte de persistance de la transaction, qui existe tout au long de la vie de la transaction. Ensuite, nous avons examiné le contexte de persistance étendue, qui peut s'étendre sur plusieurs transactions.

Comme toujours, l'exemple de code est disponible à l'adresse over sur GitHub.