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.
Clearly, manual version checking is only feasible in very trivial circumstances and not practical for most applications. Often
not only single instances, but complete graphs of modified objects have to be checked. Hibernate offers automatic version
checking with either an extended Session
or detached instances as the design paradigm.
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();
Again, Hibernate will check instance versions during flush, throwing an exception if conflicting updates occurred.
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.
Legacy database schemas are often static and can't be modified. Or, other applications might also access the same database
and don't know how to handle version numbers or even timestamps. In both cases, versioning can't rely on a particular column
in a table. To force a version check without a version or timestamp property mapping, with a comparison of the state of all
fields in a row, turn on optimistic-lock="all"
in the <class>
mapping. Note that this conceptually only works if Hibernate can compare the old and new state, i.e. if you use a single
long Session
and not session-per-request-with-detached-objects.
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().
In both cases, with dedicated version/timestamp columns or with full/dirty field comparison, Hibernate uses a single UPDATE
statement (with an appropriate WHERE
clause) per entity to execute the version check and update the information. If you use transitive persistence to cascade
reattachment to associated entities, Hibernate might execute unnecessary updates. This is usually not a problem, but on update triggers in the database might be executed even when no changes have been made to detached instances. You can customize this
behavior by setting select-before-update="true"
in the <class>
mapping, forcing Hibernate to SELECT
the instance to ensure that changes did actually occur, before updating the row.