JPA et les modes de fermeture optimistes
j'ai lu un article sur le blog D'Oracle ici à propos de JPA et des modes de verrouillage.
je ne suis pas tout à fait comprendre la différence entre OPTIMISTIC
et OPTIMISTIC_FORCE_INCREMENT
type de mode de verrouillage.
OPTIMISTIC
mode:
Lorsqu'un utilisateur verrouille une entity avec ce mode, une vérification est effectuée sur le champ de version entity (@version
) au début du mouvement et un contrôle sur le champ de version est également fait à la fin de la transaction. Si les versions sont différentes, la transaction est annulée.
OPTIMISTIC_FORCE_INCREMENT
mode:
Lorsqu'un utilisateur choisit ce mode, il doit purger() L'État D'EntityManager dans la base de données pour incrémenter manuellement le champ de version. Ainsi, toutes les autres transactions optimistes seront invalidées (annulation). De vérifier la version est également fait à la fin de l'opération de commettre ou d'annuler la transaction.
Il semble clair, mais quand dois-je utiliser OPTIMISTIC
contre OPTIMISTIC_FORCE_INCREMENT
modes ? Le seul critère que je vois, c'est pour appliquer OPTIMISTIC_FORCE_INCREMENT
mode quand je veux que la transaction ait priorité sur les autres parce que le choix de ce mode va faire reculer toutes les autres transactions en cours (Si je comprends bien le mecanisme).
y a-t-il une autre raison de choisir ce mode plutôt que OPTIMISTIC
mode?
Merci
3 réponses
normalement, vous n'utiliserez jamais l'API lock() pour un verrouillage optimiste. JPA vérifiera automatiquement toutes les colonnes de version sur toute mise à jour ou suppression.
le seul but de l'API lock() pour le verrouillage optimiste est quand votre mise à jour dépend d'un autre objet qui n'est pas modifié/mis à jour. Cela permet à votre transaction d'échouer si l'autre objet change.
Quand cela dépend de l'application et les cas d'utilisation. Optimiste permettra de s'assurer que l'autre objet a pas été mis à jour au moment de votre validation. OPTIMISTIC_FORCE_ increment s'assurera que l'autre objet n'a pas été mis à jour, et augmentera sa version sur commit.
le verrouillage optimiste est toujours vérifié sur commit, et il n'y a aucune garantie de succès avant commit. Vous pouvez utiliser flush() pour forcer les serrures de la base de données à l'avance, ou déclencher une erreur plus tôt.
N'ayez pas peur de cette longue réponse. Ce sujet n'est pas simple.
par défaut JPA impose
READ committed niveau d'isolation si vous ne spécifiez pas de verrouillage (même comportement que LockModeType.NONE
).
READ committed il faut la non-existencelecture Sale phénomène. Tout simplement T1 ne peut voir que les changements effectués par T2 après T2 commits.
utilisation d'un verrouillage optimiste dans les hausses de JPA niveau d'isolation Read Repetable.
si T1 lit certaines données au début et à la fin du mouvement, Read Repetable assure que T1 voit les mêmes données même si T2 a changé les données et engagé au milieu de T1.
et voici la partie délicate. JPA atteint Read Repetable dans la manière la plus simple possible: par prévenirNon-Repetable read phénomène. JPA is pas assez sophistiqué pour garder des instantanés de vos lectures. Il empêche simplement la deuxième lecture de se produire en augmentant une exception (si les données ont changé de la première lecture).
vous pouvez choisir entre deux options de verrouillage optimistes:
LockModeType.OPTIMISTIC
(LockModeType.READ
en JPA 1.0)LockModeType.OPTIMISTIC_FORCE_INCREMENT
(LockModeType.WRITE
en JPA 1.0)
Quelle est la différence entre les deux?
Permettez-moi de illustrer avec des exemples sur ce Person
entité.
@Entity
public class Person {
@Id int id;
@Version int version;
String name;
String label;
@OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
List<Car> cars;
// getters & setters
}
supposons maintenant que nous avons une personne nommée John stockée dans la base de données. Nous lisons cette personne dans T1 mais changer son nom en Mike dans le deuxième mouvement T2.
Sans verrouillage
Person person1 = em1.find(Person.class, id, LockModeType.NONE); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(em1.find(Person.class, id).getName()); // prints "John" - entity is already in Persistence cache
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // prints 0 - ups! don't know about any John (Non-repetable read)
Optimist read lock
Person person1 = em1.find(Person.class, id, LockModeType.OPTIMISTIC); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // OptimisticLockException - The object [Person@2ac6f054] cannot be updated because it has changed or been deleted since it was last read.
LockModeType.OPTIMISTIC_FORCE_INCREMENT
est utilisé lorsque le changement est fait pour une autre entité (peut-être une relation sans propriétaire) et nous voulons préserver l'intégrité.
Permettez-moi d'illustrer avec John acquisition d'une nouvelle voiture.
Optimist read lock
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. We'll end up with wrong info about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush();
Optimist write lock
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. That's ok though because proper locking won't let us save wrong information about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush(); // OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
bien qu'il y ait une remarque suivante dans la spécification JPA, Hibernate et EclipseLink se comportent bien et ne l'utilisent pas.
pour les objets suivis en versions, il est permis à une implémentation d'utiliser LockMode- Type.OPTIMISTIC_FORCE_ increment where LockModeType.Optimiste a été demandé, mais pas vice-versa.
Comme expliqué dans ce post,LockModeType.OPTIMICTIC
souffre de vérifier la loi de questions, afin de vous mieux vaut le coupler avec un _READ_ pessimiste ou UN_WRITE pessimiste.
d'autre part, LockModeType.OPTIMICTIC_FORCE_INCREMENT
ne souffre d'aucun problème d'incohérence de données, et vous l'utilisez généralement pour contrôler la version d'une entité mère chaque fois qu'une entité enfant est modifiée.
découvrez cet article pour plus de détails de la façon dont vous pouvez utilisez LockModeType.OPTIMICTIC_FORCE_INCREMENT
ou LockModeType.PESSIMISTIC_FORCE_INCREMENT
de sorte que la version de l'entité mère soit prise en compte des changements d'entité enfant.