Java-JPA - @ version annotation
Comment fonctionne l'annotation @Version
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 apportées au même enregistrement de banque de données. Lorsque le runtime JPA détecte une tentative de modification simultanée du même enregistrement, il lève une exception à la transaction qui tente de valider en dernier.
Mais je ne sais toujours pas comment cela fonctionne.
Également à partir de ce qui suit lignes:
Vous devriez considérer les champs de version immuables. La modification de la valeur du champ a des résultats indéfinis.
Signifie que nous devons déclarer notre champ de version comme final
?
5 réponses
Mais je ne sais toujours pas comment cela fonctionne?
Disons qu'une entité MyEntity
a 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 {[4] } sera incrémenté et ajouté à la clause WHERE
, quelque chose 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 lancera un OptimisticLockException
.
Signifie que nous devrions déclarez notre champ de version comme final
Non, mais vous pourriez envisager de protéger le setter car vous n'êtes pas censé l'appeler.
Bien que la réponse @ Pascal soit parfaitement valide, d'après mon expérience, je trouve le code ci-dessous utile pour accomplir un verrouillage optimiste:
@Entity
public class MyEntity implements Serializable {
// ...
@Version
@Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
private long version = 0L;
// ...
}
Pourquoi? Parce que:
- verrouillage Optimiste ne fonctionne pas si le champ annoté avec
@Version
est accidentellement mis ànull
. - Comme ce champ spécial n'est pas nécessairement une version métier de l'objet, pour éviter une erreur, je préfère nommer ce champ à sth comme
optlock
plutôt queversion
.
Le premier point ne fonctionne pas importe si l'application utilise uniquement JPA pour insérer des données dans la base de données, car le fournisseur JPA appliquera 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 d'unité/ intégration).
Chaque fois qu'une entité est mise à jour dans la base de données, le champ version est augmenté d'un. Chaque opération qui met à 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, le framework 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 lorsque son numéro de version a été augmenté entre load et persist.
Version utilisée pour s'assurer qu'une seule mise à jour à la fois. Le fournisseur JPA vérifiera la version, si la version attendue augmente déjà, alors quelqu'un d'autre met déjà à jour l'entité afin qu'une exception soit levée.
La mise à jour de la valeur de l'entité serait donc plus sécurisée, plus optimiste.
Si la valeur change fréquemment, vous pouvez envisager de ne pas utiliser le champ version. Pour un exemple "une entité qui a un champ de compteur, qui augmente chaque fois qu'une page web est consultée"
Juste en ajoutant un peu plus d'informations.
JPA gère la version sous le capot pour vous, mais il 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