Comment fonctionne @Version
annotations dans JPA?
J'ai trouvé diverses réponses dont l'extrait est le suivant:
JPA utilise un champ de version dans vos entités pour détecter les modifications simultanées du même enregistrement de magasin de données. Lorsque le moteur d'exécution JPA détecte une tentative de modification simultanée du même enregistrement, il lève une exception à la transaction tentant de valider en dernier.
Mais je ne sais toujours pas comment cela fonctionne.
Aussi à partir des lignes suivantes:
Vous devez considérer les champs de version immuables. Changer la valeur du champ a des résultats indéfinis.
Cela signifie-t-il que nous devrions déclarer notre champ de version comme final
?
Mais je ne suis toujours pas sûr de savoir comment cela fonctionne?
Supposons qu'une entité MyEntity
possède une propriété annotée version
:
@Entity
public class MyEntity implements Serializable {
@Id
@GeneratedValue
private Long id;
private String name;
@Version
private Long version;
//...
}
Lors de la mise à jour, le champ annoté avec @Version
sera incrémenté et ajouté à la clause WHERE
, comme ceci:
UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))
Si la clause WHERE
ne correspond pas à un enregistrement (car la même entité a déjà été mise à jour par un autre thread), le fournisseur de persistance jette un OptimisticLockException
.
Cela signifie-t-il que nous devrions déclarer notre champ de version comme définitif?
Non, mais vous pouvez envisager de protéger le poseur car vous n'êtes pas censé l'appeler.
Bien que la réponse de @Pascal soit parfaitement valide, je trouve, d'après mon expérience, que le code ci-dessous est utile pour réaliser le verrouillage optimiste:
@Entity
public class MyEntity implements Serializable {
// ...
@Version
@Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
private long version = 0L;
// ...
}
Pourquoi? Car:
@Version
Est accidentellement défini sur null
.optlock
plutôt que version
.Le premier point importe peu si l'application utilise niquement JPA pour insérer des données dans la base de données, car le fournisseur JPA appliquera le champ 0
Pour le champ @version
Au moment de la création. Mais presque toujours, des instructions SQL simples sont également utilisées (au moins pendant les tests unitaires/d'intégration).
Chaque fois qu'une entité est mise à jour dans la base de données, le champ de version est augmenté d'un. Chaque opération mettant à jour l'entité dans la base de données aura ajouté WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE
à sa requête.
En vérifiant les lignes affectées de votre opération, la structure jpa peut s’assurer qu’il n’y a pas eu de modification simultanée entre le chargement et la persistance de votre entité, car la requête ne trouverait pas votre entité dans la base de données si son numéro de version a été augmenté entre le chargement et la persistance.
Version utilisée pour garantir qu'une seule mise à jour à la fois. Le fournisseur JPA vérifiera la version. Si la version attendue augmente déjà, une autre personne mettra déjà à jour l'entité et une exception sera levée.
La mise à jour de la valeur de l'entité serait donc plus sûre et plus optimiste.
Si la valeur change fréquemment, vous pouvez envisager de ne pas utiliser le champ de version. Pour un exemple "une entité qui a un champ compteur, cela augmentera à chaque accès à une page Web"
Juste en ajoutant un peu plus d'informations.
JPA gère la version sous le capot pour vous, mais ne le fait pas lorsque vous mettez à jour votre enregistrement via JPAUpdateClause
. Dans ce cas, vous devez ajouter manuellement l'incrément de version à la requête.
Pedro