Verrouillage optimiste dans JPA

1. Introduction

Lorsqu'il s'agit d'applications d'entreprise, il est essentiel de gérer correctement l'accès simultané à une base de données. Cela signifie que nous devrions être en mesure de gérer plusieurs transactions de manière efficace et, surtout, sans erreur.

De plus, nous devons nous assurer que les données restent cohérentes entre les lectures et les mises à jour simultanées.

Pour y parvenir, nous pouvons utiliser le mécanisme de verrouillage optimiste fourni par l'API Java Persistence. Cela conduit à ce que plusieurs mises à jour effectuées sur les mêmes données en même temps n'interfèrent pas les unes avec les autres.

2. Comprendre le verrouillage optimiste

Pour utiliser le verrouillage optimiste, nous devons avoir une entité comprenant une propriété avec l' annotation @Version . Lors de son utilisation, chaque transaction qui lit des données contient la valeur de la propriété version.

Avant que la transaction ne souhaite effectuer une mise à jour, elle vérifie à nouveau la propriété de version.

Si la valeur a changé entre-temps, une exception OptimisticLockException est lancée. Sinon, la transaction valide la mise à jour et incrémente une propriété de version de valeur.

3. Verrouillage pessimiste vs verrouillage optimiste

Il est bon de savoir que, contrairement au verrouillage optimiste, JPA nous donne un verrouillage pessimiste. C'est un autre mécanisme pour gérer l'accès simultané aux données.

Nous couvrons le verrouillage pessimiste dans l'un de nos articles précédents - Verrouillage pessimiste dans JPA. Découvrons quelle est la différence et comment nous pouvons bénéficier de chaque type de verrouillage.

Comme nous l'avons déjà dit, le verrouillage optimiste est basé sur la détection des changements sur les entités en vérifiant leur attribut de version . Si une mise à jour simultanée a lieu, OptmisticLockException se produit. Après cela, nous pouvons réessayer de mettre à jour les données.

On peut imaginer que ce mécanisme convient aux applications qui font beaucoup plus de lectures que de mises à jour ou de suppressions. De plus, il est utile dans les situations où les entités doivent être détachées pendant un certain temps et où les verrous ne peuvent pas être maintenus.

Au contraire, le mécanisme de verrouillage pessimiste implique le verrouillage des entités au niveau de la base de données.

Chaque transaction peut acquérir un verrou sur les données. Tant qu'elle détient le verrou, aucune transaction ne peut lire, supprimer ou mettre à jour les données verrouillées. Nous pouvons supposer que l'utilisation d'un verrouillage pessimiste peut entraîner des blocages. Cependant, il garantit une plus grande intégrité des données qu'un verrouillage optimiste.

4. Attributs de version

Les attributs de version sont des propriétés avec l' annotation @Version . Ils sont nécessaires pour permettre un verrouillage optimiste. Voyons un exemple de classe d'entité:

@Entity public class Student { @Id private Long id; private String name; private String lastName; @Version private Integer version; // getters and setters }

Il y a plusieurs règles que nous devons suivre lors de la déclaration des attributs de version:

  • chaque classe d'entité ne doit avoir qu'un seul attribut de version
  • il doit être placé dans la table primaire pour une entité mappée sur plusieurs tables
  • le type d'un attribut de version doit être l'un des suivants: int , Integer , long , Long , short , Short , java.sql.Timestamp

Nous devons savoir que nous pouvons récupérer une valeur de l'attribut version via une entité, mais nous ne devons pas la mettre à jour ou l'incrémenter. Seul le fournisseur de persistance peut le faire, les données restent donc cohérentes.

Il convient de noter que les fournisseurs de persistance sont capables de prendre en charge le verrouillage optimiste pour les entités qui n'ont pas d'attributs de version. Pourtant, c'est une bonne idée de toujours inclure les attributs de version lorsque vous travaillez avec un verrouillage optimiste.

Si nous essayons de verrouiller une entité qui ne contient pas un tel attribut et que le fournisseur de persistance ne le prend pas en charge, cela entraînera une PersitenceException .

5. Modes de verrouillage

JPA nous fournit deux modes de verrouillage optimiste différents (et deux alias):

  • OPTIMISTIQUE - il obtient un verrou de lecture optimiste pour toutes les entités contenant un attribut de version
  • OPTIMISTIC_FORCE_INCREMENT - il obtient un verrou optimiste identique à OPTIMISTIC et incrémente en outre la valeur de l'attribut de version
  • LIRE - c'est un synonyme de OPTIMISTIQUE
  • WRITE - c'est un synonyme de OPTIMISTIC_FORCE_INCREMENT

Nous pouvons trouver tous les types répertoriés ci-dessus dans la classe LockModeType .

5.1. OPTIMISTIQUE ( LIRE )

Comme nous le savons déjà, les modes de verrouillage OPTIMISTIC et READ sont des synonymes. Cependant, la spécification JPA nous recommande d'utiliser OPTIMISTIC dans de nouvelles applications.

Chaque fois que nous demandons le mode de verrouillage OPTIMISTIC , un fournisseur de persistance empêchera nos données de lectures sales ainsi que de lectures non répétables .

En termes simples, cela devrait garantir que toute transaction échoue à valider toute modification sur les données d'une autre transaction:

  • a mis à jour ou supprimé mais non validé
  • a été mis à jour ou supprimé avec succès entre-temps

5.2. OPTIMISTIC_INCREMENT ( WRITE )

Comme précédemment, OPTIMISTIC_INCREMENT et WRITE sont des synonymes, mais le premier est préférable.

OPTIMISTIC_INCREMENT doit remplir les mêmes conditions que le mode de verrouillage OPTIMISTIC . En outre, il incrémente la valeur d'un attribut de version. Cependant, il n'est pas spécifié si cela doit être fait immédiatement ou peut être reporté jusqu'à la validation ou au vidage.

Il vaut la peine de savoir qu'un fournisseur de persistance est autorisé à fournir OPTIMISTIC_INCREMENT fonctionnalité lorsque OPTIMISTE mode de verrouillage est demandé.

6. Utilisation du verrouillage optimiste

We should remember that for versioned entities optimistic locking is available by default. Yet there are several ways of requesting it explicitly.

6.1. Find

To request optimistic locking we can pass the proper LockModeType as an argument to find method of EntityManager:

entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);

6.2. Query

Another way to enable locking is using the setLockMode method of Query object:

Query query = entityManager.createQuery("from Student where id = :id"); query.setParameter("id", studentId); query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT); query.getResultList()

6.3. Explicit Locking

We can set a lock by calling EnitityManager's lock method:

Student student = entityManager.find(Student.class, id); entityManager.lock(student, LockModeType.OPTIMISTIC);

6.4. Refresh

We can call the refresh method the same way as the previous method:

Student student = entityManager.find(Student.class, id); entityManager.refresh(student, LockModeType.READ);

6.5. NamedQuery

The last option is to use @NamedQuery with the lockMode property:

@NamedQuery(name="optimisticLock", query="SELECT s FROM Student s WHERE s.id LIKE :id", lockMode = WRITE)

7. OptimisticLockException

When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException. We should be aware that due to the exception the active transaction is always marked for rollback.

It's good to know how we can react to OptimisticLockException. Conveniently, this exception contains a reference to the conflicting entity. However, it's not mandatory for the persistence provider to supply it in every situation. There is no guarantee that the object will be available.

There is a recommended way of handling the described exception, though. We should retrieve the entity again by reloading or refreshing. Preferably in a new transaction. After that, we can try to update it once more.

8. Conclusion

In this tutorial, we got familiar with a tool which can help us orchestrate concurrent transactions. Optimistic locking uses version attributes included in entities to control concurrent modifications on them.

Therefore, it ensures that any updates or deletes won't be overwritten or lost silently. Opposite to pessimistic locking, it doesn't lock entities on the database level and consequently, it isn't vulnerable to DB deadlocks.

Nous avons appris que le verrouillage optimiste est activé par défaut pour les entités versionnées. Cependant, il existe plusieurs façons de le demander explicitement en utilisant différents types de mode de verrouillage.

Un autre fait dont nous devons nous souvenir est que chaque fois qu'il y a des mises à jour conflictuelles sur des entités, nous devons nous attendre à une OptimisticLockException .

Enfin, le code source de ce tutoriel est disponible à l'adresse over sur GitHub.