J'ai passé ma matinée à lire tous les meilleurs articles qui Google se tourne vers le verrouillage optimiste , et pour la vie de moi, je ne comprends toujours pas vraiment.
I comprendre que le verrouillage optimiste implique l'ajout d'une colonne pour suivre la "version" de l'enregistrement, et que cette colonne peut être un horodatage, un compteur ou toute autre construction de suivi de version. Mais je ne comprends toujours pas comment cela garantit l'intégrité de WRITE (ce qui signifie que si plusieurs processus mettent à jour la même entité en même temps, qu'ensuite, l'entité reflète correctement le véritable état dans lequel elle devrait être).
Quelqu'un peut-il fournir un exemple concret et facile à comprendre de la façon dont le verrouillage optimiste pourrait être utilisé dans Java (contre , peut-être, une base de données MySQL). Disons que nous avons une entité Person
:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
}
Et que les instances de Person
deviennent persistantes dans une table MySQL people
:
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL # Say we also have a colors table and people has a 1:1 relationship with it
);
Supposons maintenant qu'il y ait 2 systèmes logiciels, ou 1 système avec 2 threads dessus, qui essaient de mettre à jour la même entité Person
en même temps:
people
et/ou colors
? (Recherche d'exemple DDL spécifique)people
/colors
tables correctement? Fondamentalement, je cherche à voir le verrouillage optimiste en action, avec une explication facile à suivre de son fonctionnement.Normalement, lorsque vous examinez le verrouillage optimiste, vous utilisez également une bibliothèque comme Hibernate ou une autre implémentation JPA avec @Version
prise en charge.
L'exemple pourrait se lire comme ceci:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
@Version
private Long version;
}
alors qu'évidemment il est inutile d'ajouter une annotation @Version
si vous n'utilisez pas un framework qui le supporte.
Le DDL pourrait alors être
CREATE TABLE people (
person_id PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL, # } I realize these column defs are not valid but this is just pseudo-code
age INT NOT NULL,
color_id FOREIGN KEY (colors) NOT NULL, # Say we also have a colors table and people has a 1:1 relationship with it
version BIGINT NOT NULL
);
Pour que les deux étapes soient effectuées sans risquer qu'un autre processus modifie les données entre les deux étapes, elles sont normalement gérées via une instruction comme
UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
Après avoir exécuté l'instruction, vous vérifiez si vous avez mis à jour une ligne ou non. Si vous l'avez fait, personne d'autre n'a changé les données depuis que vous les avez lues, sinon quelqu'un d'autre a changé les données. Si quelqu'un d'autre a modifié les données, vous recevrez normalement un OptimisticLockException
de la bibliothèque que vous utilisez.
Cette exception devrait entraîner la révocation de toutes les modifications et le redémarrage du processus de modification de la valeur car la condition à laquelle l'entité devait être mise à jour pourrait ne plus être applicable.
Donc pas de collision:
Collision:
Si la couleur est un autre objet, vous devez y mettre une version selon le même schéma.
OptimisticLockException
sSi de nombreuses applications différentes accèdent à vos données, il est préférable d'utiliser une colonne mise à jour automatiquement par la base de données. par exemple. pour MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
de cette façon, les applications implémentant un verrouillage optimiste remarqueront des modifications par des applications stupides.
Si vous mettez à jour des entités plus souvent que la résolution de TIMESTAMP
ou son interprétation Java, cette approche peut ne pas détecter certains changements. De plus, si vous laissez Java générer le nouveau TIMESTAMP
, vous devez vous assurer que toutes les machines exécutant vos applications sont parfaitement synchronisées dans le temps.
Si toutes vos applications peuvent être modifiées en entier, long, ... la version est normalement une bonne solution car elle ne souffrira jamais d'horloges réglées différemment ;-)
Il existe d'autres scénarios. Vous pourriez par exemple utiliser un hachage ou même générer aléatoirement un String
chaque fois qu'une ligne doit être modifiée. L'important est que vous ne répétiez pas les valeurs pendant qu'un processus contient des données pour le traitement local ou à l'intérieur d'un cache car ce processus ne pourra pas détecter de changement en regardant la colonne version.
En dernier recours, vous pouvez utiliser la valeur de tous les champs comme version. Bien que ce soit l'approche la plus coûteuse dans la plupart des cas, c'est un moyen d'obtenir des résultats similaires sans changer la structure de la table. Si vous utilisez Hibernate, il y a l'annotation @OptimisticLocking
- pour appliquer ce comportement. Utilisez @OptimisticLocking(type = OptimisticLockType.ALL)
sur la classe d'entité pour échouer si une ligne a changé depuis que vous avez lu l'entité ou @OptimisticLocking(type = OptimisticLockType.DIRTY)
pour échouer simplement lorsqu'un autre processus a également modifié les champs que vous avez modifiés.
@TheConstructor: Grande explication de ce que c'est et de ce qu'il n'est pas. Lorsque vous avez dit "Le verrouillage optimiste n'est pas une magie pour fusionner des modifications conflictuelles", je voulais commenter. J'avais l'habitude de gérer une application DataFlex qui permettait aux utilisateurs de modifier des enregistrements sur un formulaire. Lorsqu'ils appuyaient sur le bouton "Enregistrer", l'application faisait ce qu'on appelait une "relecture multi-utilisateurs" des données - en tirant les valeurs actuelles - et comparait à ce que l'utilisateur avait modifié. Si les champs modifiés par l'utilisateur n'avaient pas été modifiés entre-temps, seuls ces champs seraient réécrits dans l'enregistrement (qui n'était verrouillé que pendant l'opération de relecture + écriture) et, par conséquent, 2 utilisateurs pouvaient modifier de manière transparente différents champs sur le même record sans problèmes. Il ne nécessitait pas de tampons de version, juste une connaissance des champs qui ont été modifiés.
Certes, ce n'est pas une solution parfaite, mais cela a fait l'affaire dans ce cas. Il était optimiste et permettait des modifications non liées et il donnait à l'utilisateur une erreur sur les modifications conflictuelles. C'était le meilleur qui puisse être fait, pré-SQL, mais c'est toujours un bon principe de conception aujourd'hui, peut-être pour plus de scénarios liés à l'objet ou au Web.