L'un des principaux avantages du mécanisme de contrôle des accès concurrents d'Hibernate est qu'il est très facile à comprendre. Hibernate utilise directement les connexions JDBC ainsi que les ressources JTA sans y ajouter davantage de mécanisme de blocage. Nous vous recommandons de vous familiariser avec les spécifications JDBC, ANSI et d'isolement de transaction de la base de données que vous utilisez.
Hibernate ne vérouille pas vos objets en mémoire. Votre application peut suivre le comportement défini par le niveau d'isolation
de vos transactions de base de données. Notez que grâce à la Session
, qui est aussi un cache de scope transaction, Hibernate fournit des lectures répétées pour les récupération par identifiants
et les requêtes d'entités (pas celle de valeurs scalaires).
En addition au versionning pour le controle automatique de concurrence, Hibernate fournit une API (mineure) pour le verrouillage
perssimiste des enregistrements, en générant une syntaxe SELECT FOR UPDATE
. Le controle de concurrence optimiste et cette API seront détaillés plus tard dans ce chapitre.
Nous aborderons la gestion des accès concurrents en discutant de la granularité des objets Configuration
, SessionFactory
, et Session
, ainsi que de certains concepts relatifs à la base de données et aux longues transactions applicatives.
Il est important de savoir qu'un objet SessionFactory
est un objet complexe et optimisé pour fonctionner avec les threads(thread- safe). Il est coûteux à créer et est ainsi prévu
pour n'être instancié qu?une seule fois via un objet Configuration
au démarrage de l'application, et être partagé par tous les threads d'une application.
Un objet Session
est relativement simple et n'est threadsafe. Il est également peu coûteux à créer. Il devrait n'être utilisé qu'une seule
fois, pour un processus d'affaire ou une unité de travail ou une conversation et ensuite être relâché. Un objet Session
ne tentera pas d'obtenir de connexion ( Connection
) JDBC (ou de Datasource
) si ce n'est pas nécessaire.
Afin de compléter ce tableau, vous devez également penser aux transactions de base de données. Une transaction de base de données se doit d'être la plus courte possible afin de réduire les risques de collision sur des enregistrements verrouillés. De longues transactions à la base de données nuiront à l'extensibilité de vos applications lorsque confrontées à de hauts niveaux de charge. Par conséquent, il n'est jamais bon de maintenir une transaction ouverte pendant la durée de reflexion de l'utilisateur, jusqu'a ce que l'unité de travail soit achevée.
Maintenant, comment délimiter une unité de travail? Est-ce qu'une instance de Session
peut avoir une durée de vie dépassant plusieurs transactions à la base de données, ou bien est-ce que celles-ci doivent être
liées une à une? Quand faut-il ouvrir et fermer une Session
? Comment définir la démarcation de vos transactions à la base de données?
Il est important de mentionner que d'utiliser un paradigme session-par-operation est un anti-pattern. Autrement dit: n'ouvrez et ne fermez pas la Session
à chacun de vos accès simples à la base de données dans un même thread! Bien sûr, le même raisonnement s'applique sur la
gestion des transactions à la base de données. Les appels à la base de données devraient être faits en ordre et selon une
séquence définie. Ils devraient également être regroupés en des unités de travail atomiques. (Notez que l?utilisation d?une
connexion auto-commit constitue le même anti-pattern. Ce mode de fonctionnement existe pour les applications émettant des
commandes SQL à partir d?une console. Hibernate désengage le mode auto-commit et s'attend à ce qu'un serveur d'applications
le fasse également.) Les transactions avec la base de données ne sont jamais optionnelles, toute communication avec une base
de données doit se dérouler dans une transaction, peu importe si vous lisez ou écrivez des données. Comme évoqué, le comportement
auto-commit pour lire les données devrait être évité, puisque plusieurs petites transactions ne seront jamais aussi efficaces
qu'une seule plus grosse clairement définie comme unité de travail. Ce dernier choix et en plus beaucoup plus facile a maintenir
et à faire évoluer.
The most common pattern in a multi-user client/server application is session-per-request. In this model, a request from the client is sent to the server (where the Hibernate persistence layer runs), a new Hibernate
Session
is opened, and all database operations are executed in this unit of work. Once the work has been completed (and the response
for the client has been prepared), the session is flushed and closed. You would also use a single database transaction to
serve the clients request, starting and committing it when you open and close the Session
. The relationship between the two is one-to-one and this model is a perfect fit for many applications.
The challenge lies in the implementation. Hibernate provides built-in management of the "current session" to simplify this
pattern. All you have to do is start a transaction when a server request has to be processed, and end the transaction before
the response is sent to the client. You can do this in any way you like, common solutions are ServletFilter
, AOP interceptor with a pointcut on the service methods, or a proxy/interception container. An EJB container is a standardized
way to implement cross-cutting aspects such as transaction demarcation on EJB session beans, declaratively with CMT. If you
decide to use programmatic transaction demarcation, prefer the Hibernate Transaction
API shown later in this chapter, for ease of use and code portability.
Votre application peut accéder la "session courante" pour exécuter une requête en invoquant simplement sessionFactory.getCurrentSession()
n'importe où et autant de fois que souhaité. Vous obtiendrez toujours une Session
dont le scope est la transaction courante avec la base de données. Ceci doit être configuré soit dans les ressources local
ou dans l'environnement JTA, voir Section 2.5, « Sessions Contextuelles ».
Il est parfois utile d'étendre le scope d'une Session
et d'une transaction à la base de données jusqu'à ce que "la vue soit rendue". Ceci est particulièrement utile dans des applications
à base de servlet qui utilisent une phase de rendue séparée une fois que la réponse a été préparée. Etendre la transaction
avec la base de données jusqu'à la fin du rendering de la vue est aisé si vous implémentez votre propre intercepteur. Cependant,
ce n'est pas facile si vous vous appuyez sur les EJBs avec CMT, puisqu'une transaction sera achevée au retour de la méthode
EJB, avant le rendu de la vue. Rendez vous sur le site Hibernate et sur le forum pour des astuces et des exemples sur le pattern
Open Session in View pattern..
Le paradigme session-per-request n'est pas le seul élément à utiliser dans le design de vos unités de travail. Plusieurs processus d'affaire requièrent toute une série d'interactions avec l'utilisateur, entrelacées d'accès à la base de donnée. Dans une application Web ou une application d'entreprise, il serait inacceptable que la durée de vie d'une transaction s'étale sur plusieurs interactions avec l'usager. Considérez l'exemple suivant:
Un écran s'affiche. Les données vues par l'usager ont été chargées dans l'instance d'un objet Session
, dans le cadre d'une transaction de base de données. L'usager est libre de modifier ces objets.
L'usager clique "Sauvegarder" après 5 minutes et souhaite persister les modifications qu'il a apportées. Il s'attend à être la seule personne a avoir modifié ces données et qu'aucune modification conflictuelle ne se soit produite durant ce laps de temps.
Ceci s'appelle une unité de travail. Du point de vue de l'utilisateur: une conversation (ou transaction d'application). Il y a plusieurs façon de mettre ceci en place dans votre application.
Une première implémentation naïve pourrait consister à garder la Session
et la transaction à la base de données ouvertes durant le temps de travail de l'usager, à maintenir les enregistrements verrouillés
dans la base de données afin d'éviter des modifications concurrentes et de maintenir l'isolation et l'atomicité de la transaction
de l'usager. Ceci est un anti-pattern à éviter, puisque le verrouillage des enregistrements dans la base de données ne permettrait
pas à l'application de gérer un grand nombre d'usagers concurrents.
Clearly, we have to use several database transactions to implement the conversation. In this case, maintaining isolation of business processes becomes the partial responsibility of the application tier. A single conversation usually spans several database transactions. It will be atomic if only one of these database transactions (the last one) stores the updated data, all others simply read data (e.g. in a wizard-style dialog spanning several request/response cycles). This is easier to implement than it might sound, especially if you use Hibernate's features:
Automatic Versioning - Hibernate can do automatic optimistic concurrency control for you, it can automatically detect if a concurrent modification occurred during user think time. Usually we only check at the end of the conversation.
Objets Détachés - Si vous décidez d'utiliser le paradigme session-par-requête discuté plus haut, toutes les entités chargées en mémoire deviendront des objets détachés durant le temps de réflexion de l'usager. Hibernate vous permet de rattacher ces objets et de persister les modifications y ayant été apportées. Ce pattern est appelé: session-per- request-with-detached-objects (littéralement: session- par-requête-avec-objets-détachés). Le versionnage automatique est utilisé afin d'isoler les modifications concurrentes.
Extended (or Long) Session - The Hibernate Session
may be disconnected from the underlying JDBC connection after the database transaction has been committed, and reconnected
when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. Automatic versioning is used to isolate concurrent modifications and the Session
is usually not allowed to be flushed automatically, but explicitly.
Les deux patterns session-per-request-with- detached- objects (session-par-requête-avec-objets- détachés) et session-per-conversation (session-par-conversation) ont chacun leurs avantages et désavantages qui seront exposés dans ce même chapitre, dans la section au sujet du contrôle optimiste de concurrence.
Une application peut accéder à la même entité persistante de manière concurrente dans deux Session
s différentes. Toutefois, une instance d'une classe persistante n'est jamais partagée par deux instances distinctes de la
classe Session
. Il existe donc deux notions de l'identité d'un objet:
foo.getId().equals( bar.getId() )
foo==bar
Then for objects attached to a particular Session
(i.e. in the scope of a Session
) the two notions are equivalent, and JVM identity for database identity is guaranteed by Hibernate. However, while the application
might concurrently access the "same" (persistent identity) business object in two different sessions, the two instances will
actually be "different" (JVM identity). Conflicts are resolved using (automatic versioning) at flush/commit time, using an
optimistic approach.
Cette approche permet de reléguer à Hibernate et à la base de données sous-jacente le soin de gérer les problèmes d'accès
concurrents. Cette manière de faire assure également une meilleure extensibilité de l'application puisque assurer l'identité
JVM dans un thread ne nécessite pas de mécanismes de verrouillage coûteux ou d'autres dispositifs de synchronisation. Une
application n'aura jamais le besoin de synchroniser des objets d'affaire tant qu'elle peut garantir qu'un seul thread aura
accès à une instance de Session
. Dans le cadre d'exécution d'un objet Session
, l'application peut utiliser en toute sécurité ==
pour comparer des objets.
Une application qui utiliserait ==
à l'extérieur du cadre d'exécution d'une Session
pourrait obtenir des résultats inattendus et causer certains effets de bords. Par exemple, si vous mettez 2 objets dans le
même Set
, ceux-ci pourraient avoir la même identité BD (i.e. ils représentent le même enregistrement), mais leur identité JVM pourrait
être différente (elle ne peut, par définition, pas être garantie sur deux objets détachés). Le développeur doit donc redéfinir
l'implémentation des méthodes equals()
et hashcode()
dans les classes persistantes et y adjoindre sa propre notion d'identité. Il existe toutefois une restriction: Il ne faut
jamais utiliser uniquement l'identifiant de la base de données dans l'implémentation de l'égalité; Il faut utiliser une clé
d'affaire, généralement une combinaison de plusieurs attributs uniques, si possible immuables. Les identifiants de base de
données vont changer si un objet transitoire (transient) devient persistant. Si une instance transitoire est contenue dans
un Set
, changer le hashcode brisera le contrat du Set
. Les attributs pour les clés d'affaire n'ont pas à être aussi stables que des clés primaires de bases de données. Il suffit
simplement qu'elles soient stables tant et aussi longtemps que les objets sont dans le même Set
. Veuillez consulter le site web Hibernate pour des discussions plus pointues à ce sujet. Notez que ce concept n'est pas
propre à Hibernate mais bien général à l'implémentation de l'identité et de l'égalité en Java.
Bien qu'il puisse y avoir quelques rares exceptions à cette règle, il est recommandé de ne jamais utiliser les anti-patterns session-per- user-session et session-per-application . Vous trouverez ici- bas quelques problèmes que vous risquez de rencontrer si vous en faite l?utilisation. (Ces problèmes pourraient quand même survenir avec des patterns recommandés) Assurez-vous de bien comprendre les implications de chacun des patterns avant de prendre votre décision.
L'objet Session
n?est pas conçu pour être utilisé par de multiples threads. En conséquence, les objets potentiellement multi-thread comme
les requêtes HTTP, les EJB Session et Swing Worker, risquent de provoquer des conditions de course dans la Session
si celle-ci est partagée. Dans un environnement web classique, il serait préférable de synchroniser les accès à la session
http afin d?éviter qu?un usager ne recharge une page assez rapidement pour que deux requêtes s?exécutant dans des threads
concurrents n?utilisent la même Session
.
Lorsque Hibernate lance une exception, le roll back de la transaction en cours doit être effectué et la Session
doit être immédiatement fermée. (Ceci sera exploré plus tard dans le chapitre.) Si la Session
est directement associée à une application, il faut arrêter l?application. Le roll back de la transaction ne remettra pas
les objets dans leur état du début de la transaction. Ainsi, ceux-ci pourraient être désynchronisés d?avec les enregistrements.
(Généralement, cela ne cause pas de réels problèmes puisque la plupart des exceptions sont non traitables et requièrent la
reprise du processus d?affaire ayant échoué.)
La Session
met en mémoire cache tous les objets persistants (les objets surveillés et dont l'état est géré par Hibernate.) Si la Session
est ouverte indéfiniment ou si une trop grande quantité d'objets y est chargée, l?utilisation de la mémoire peut potentiellement
croître jusqu?à atteindre le maximum allouable à l?application (java.lang.OutOfMemoryError.) Une solution à ce problème est
d?appeler les méthodes Session.clear()
et Session.evict()
pour gérer la mémoire cache de la Session
. Vous pouvez également utiliser des stored procedures si vous devez lancer des traitements sur de grandes quantités d?informations.
Certaines solutions sont décrites ici : Chapitre 13, Traitement par paquet . Garder une Session
ouverte pour toute la durée d?une session usager augmente également considérablement le risque de travailler avec de l?information
périmée.