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:

enter image description here

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:

enter image description here

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

18
demandé sur flooose 2012-11-27 13:59:01

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.

10
répondu James 2012-11-27 15:09:06

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.

39
répondu zbig 2016-12-28 12:26:43

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.

0
répondu Vlad Mihalcea 2018-01-06 10:11:56