La gestion optimiste des accès concurrents avec versionnage est la seule approche pouvant garantir l'extensibilité des applications à haut niveau de charge. Le système de versionnage utilise des numéros de version ou l'horodatage pour détecter les mises à jour causant des conflits avec d'autres actualisations antérieures. Hibernate propose trois approches pour l'écriture de code applicatif utilisant la gestion optimiste d'accès concurrents. Le cas d'utilisation décrit plus bas fait mention de conversation, mais le versionnage peut également améliorer la qualité d'une application en prévenant la perte de mises à jour.
Dans cet exemple d'implémentation utilisant peu les fonctionnalités d'Hibernate, chaque interaction avec la base de données
se fait en utilisant une nouvelle Session
et le développeur doit recharger les données persistantes à partir de la BD avant de les manipuler. Cette implémentation
force l'application à vérifier la version des objets afin de maintenir l'isolation transactionnelle. Cette approche, semblable
à celle retrouvée pour les EJB, est la moins efficace de celles présentées dans ce chapitre.
// foo is an instance loaded by a previous Session session = factory.openSession(); Transaction t = session.beginTransaction(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); // load the current state if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException(); foo.setProperty("bar"); t.commit(); session.close();
Le mapping de la propriété version
est fait via <version>
et Hibernate l'incrémentera automatiquement à chaque flush() si l'entité doit être mise à jour.
Bien sûr, si votre application ne fait pas face à beaucoup d'accès concurrents et ne nécessite pas l'utilisation du versionnage, cette approche peut également être utilisée, il n'y a qu'à ignorer le code relié au versionnage. Dans ce cas, la stratégie du last commit wins (littéralement: le dernier commit l'emporte) sera utilisée pour les conversations (longues transactions applicatives). Gardez à l'esprit que cette approche pourrait rendre perplexe les utilisateurs de l'application car ils pourraient perdre des données mises à jour sans qu'aucun message d'erreur ne leur soit présenté et sans avoir la possibilité de fusionner les données.
Il est clair que la gestion manuelle de la vérification du versionnage des objets ne peut être effectuée que dans certains cas triviaux et que cette approche n'est pas valable pour la plupart des applications. De manière générale, les applications ne cherchent pas à actualiser de simples objets sans relations, elles le font généralement pour de larges graphes d'objets. Pour toute application utilisant le paradigme des conversations ou des objets détachés, Hibernate peut gérer automatiquement la vérification des versions d'objets.
Dans ce scénario, une seule instance de Session
et des objets persistants est utilisée pour toute l'application. Hibernate vérifie la version des objets persistants avant
d'effectuer le flush() et lance une exception si une modification concurrente est détectée. Il appartient alors au développeur
de gérer l'exception. Les traitements alternatifs généralement proposés sont alors de permettre à l'usager de faire la fusion
des données ou de lui offrir de recommencer son travail à partie des données les plus récentes dans la BD.
Il est à noter que lorsqu'une application est en attente d'une action de la part de l?usager, La Session
n'est pas connectée à la couche JDBC sous-jacente. C'est la manière la plus efficace de gérer les accès à la base de données.
L'application ne devrait pas se préoccuper du versionnage des objets, de la réassociation des objets détachés, ni du rechargement
de tous les objets à chaque transaction.
// foo is an instance loaded earlier by the old session Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction foo.setProperty("bar"); session.flush(); // Only for last transaction in conversation t.commit(); // Also return JDBC connection session.close(); // Only for last transaction in conversation
L'objet foo
sait quel objet Session
l'a chargé. Session.reconnect()
obtient une nouvelle connexion (celle-ci peut être également fournie) et permet à la session de continuer son travail. La
méthode Session.disconnect()
déconnecte la session de la connexion JDBC et retourne celle-ci au pool de connexion (à moins que vous ne lui ayez fourni
vous même la connexion.) Après la reconnexion, afin de forcer la vérification du versionnage de certaines entités que vous
ne cherchez pas à actualiser, vous pouvez faire un appel à Session.lock()
en mode LockMode.READ
pour tout objet ayant pu être modifié par une autre transaction. Il n'est pas nécessaire de verrouiller les données que vous
désirez mettre à jour.
Si des appels implicites aux méthodes disconnect()
et reconnect()
sont trop coûteux, vous pouvez les éviter en utilisant hibernate.connection.release_mode
.
Ce pattern peut présenter des problèmes si la Session
est trop volumineuse pour être stockée entre les actions de l'usager. Plus spécifiquement, une session HttpSession
se doit d'être la plus petite possible. Puisque la Session
joue obligatoirement le rôle de mémoire cache de premier niveau et contient à ce titre tous les objets chargés, il est préférable
de n'utiliser cette stratégie que pour quelques cycles de requêtes car les objets risquent d'y être rapidement périmés.
Notez que la Session
déconnectée devrait être conservée près de la couche de persistance. Autrement dit, utilisez un EJB stateful pour conserver
la Session
et évitez de la sérialiser et de la transférer à la couche de présentation (i.e. Il est préférable de ne pas la conserver
dans la session HttpSession
.)
The extended session pattern, or session-per-conversation, is more difficult to implement with automatic current session context management. You need to supply your own implementation
of the CurrentSessionContext
for this, see the Hibernate Wiki for examples.
Chaque interaction avec le système de persistance se fait via une nouvelle Session
. Toutefois, les mêmes instances d'objets persistants sont réutilisées pour chacune de ces interactions. L'application doit
pouvoir manipuler l'état des instances détachées ayant été chargées antérieurement via une autre session. Pour ce faire, ces
objets persistants doivent être rattachés à la Session
courante en utilisant Session.update()
, Session.saveOrUpdate()
, ou Session.merge()
.
// foo is an instance loaded by a previous Session foo.setProperty("bar"); session = factory.openSession(); Transaction t = session.beginTransaction(); session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already t.commit(); session.close();
Encore une fois, Hibernate vérifiera la version des instances devant être actualisées durant le flush(). Une exception sera lancée si des conflits sont détectés.
Vous pouvez également utiliser lock()
au lieu de update()
et utiliser le mode LockMode.READ
(qui lancera une vérification de version, en ignorant tous les niveaux de mémoire cache) si vous êtes certain que l'objet
n'a pas été modifié.
Vous pouvez désactiver l'incrémentation automatique du numéro de version de certains attributs et collections en mettant la
valeur du paramètre de mapping optimistic-lock
à false. Hibernate cessera ainsi d'incrémenter leur numéro de version s'ils sont mis à jour.
Certaines entreprises possèdent de vieux systèmes dont les schémas de bases de données sont statiques et ne peuvent être modifiés.
Il existe aussi des cas où plusieurs applications doivent accéder à la même base de données, mais certaines d'entre elles
ne peuvent gérer les numéros de version ou les champs horodatés. Dans les deux cas, le versionnage ne peut être implanté par
le rajout d'une colonne dans la base de données. Afin de forcer la vérification de version dans un système sans en faire le
mapping, mais en forçant une comparaison des états de tous les attributs d'une entité, vous pouvez utiliser l'attribut optimistic- lock="all"
sous l'élément <class>
. Veuillez noter que cette manière de gérer le versionnage ne peut être utilisée que si l'application utilises de longues
sessions, lui permettant de comparer l'ancien état et le nouvel état d'une entité. L'utilisation d'un pattern session-per-request-with-detached- objects
devient alors impossible.
Il peut être souhaitable de permettre les modifications concurrentes lorsque des champs distincts sont modifiés. En mettant
la propriété optimistic-lock="dirty"
dans l'élément <class>
, Hibernate ne fera la comparaison que des champs devant être actualisés lors du flush().
Dans les deux cas: en utilisant une colonne de version/horodatée ou via la comparaison de l'état complet de l'objet ou de
ses champs modifiés, Hibernate ne créera qu'une seule commande d'UPDATE par entité avec la clause WHERE appropriée pour mettre
à jour l'entité ET en vérifier la version. Si vous utilisez la persistance transitive pour propager l'évènement de rattachement à des entités
associées, il est possible qu'Hibernate génère des commandes d'UPDATE inutiles. Ceci n'est généralement pas un problème, mais
certains déclencheurs on update dans la base de données pourraient être activés même si aucun changement n'était réellement persisté sur des objets associés.
Vous pouvez personnaliser ce comportement en indiquant select-before- update="true"
dans l'élément de mapping <class>
. Ceci forcera Hibernate à faire le SELECT de l'instance afin de s'assurer que l'entité doit réellement être actualisée avant
de lancer la commande d'UPDATE.