web-dev-qa-db-fra.com

Exemple de verrouillage optimiste par béton (Java)

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:

  • Le logiciel/thread # 1 tente de conserver un changement de nom de famille (de " John Smith" à " John Doe")
  • Le logiciel/thread # 2 essaie de conserver un changement dans la couleur préférée (de [~ # ~] rouge [~ # ~] à [~ # ~] vert [~ # ~])

Mes questions:

  1. Comment un verrouillage optimiste pourrait-il être implémenté sur les tables people et/ou colors? (Recherche d'exemple DDL spécifique)
  2. Comment pourriez-vous alors utiliser ce verrouillage optimiste au niveau de la couche application/Java? (Recherche d'un exemple de code spécifique)
  3. Quelqu'un peut-il me faire passer par un scénario dans lequel les modifications DDL/code (de # 1 et # 2 ci-dessus) entreraient en jeu dans mon scénario (ou tout autre scénario) et "verrouilleraient de manière optimiste" le people/colors tables correctement? Fondamentalement, je cherche à voir le verrouillage optimiste en action, avec une explication facile à suivre de son fonctionnement.
29

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
);

Que se passe-t-il avec la version?

  1. Chaque fois avant de stocker l'entité, vous vérifiez si la version stockée dans la base de données est toujours la version que vous connaissez.
  2. Si c'est le cas, stockez vos données avec une version incrémentée d'une unité

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:

  1. Le processus A lit la personne
  2. Le processus A écrit une personne incrémentant ainsi la version
  3. Le processus B lit la personne
  4. Le processus B écrit la personne incrémentant ainsi la version

Collision:

  1. Le processus A lit la personne
  2. Le processus B lit la personne
  3. Le processus A écrit une personne incrémentant ainsi la version
  4. Le processus B reçoit une exception lors de la tentative d'enregistrement car la version a changé depuis la lecture de Personne

Si la couleur est un autre objet, vous devez y mettre une version selon le même schéma.

Qu'est-ce qui n'est pas le verrouillage optimiste?

  • Le verrouillage optimiste n'est pas magique pour fusionner des modifications conflictuelles. Le verrouillage optimiste empêchera simplement les processus d'écraser accidentellement les modifications d'un autre processus.
  • Le verrouillage optimiste n'est en fait pas un vrai DB-Lock. Cela fonctionne simplement en comparant la valeur de la colonne de version. Vous n'empêchez pas d'autres processus d'accéder à des données, alors attendez-vous à obtenir OptimisticLockExceptions

Quel type de colonne utiliser comme version?

Si 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.

39
TheConstructor

@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.

4
no comprende