Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 13. Transactions et Accès concurrents

L'un des principaux avantages du mécanisme de contrôle des accès concurrents de 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 du système de gestion de la base de données que vous utilisez.

Hibernate ne verrouille 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 portée de transaction, Hibernate fournit des lectures répétées pour les recherches par identifiants et les requêtes d'entités (ne rapporte pas les requêtes qui retournent des valeurs scalaires).

En plus du versioning, pour le contrôle automatique optimiste de concurrence, Hibernate fournit également une API (mineure) pour le verrouillage pessimiste des lignes, en générant une syntaxe `SELECT FOR UPDATE`. Le contrôle de concurrence optimiste et cette API seront approfondis ultérieurement dans ce chapitre.

Nous abordons la gestion des accès concurrents en discutant de la granularité des objets `Configuration`, `SessionFactory`, et `Session`, ainsi que des transactions de la base de données et des longues transactions applicatives.

## 13.1. Portées des sessions et des transactions

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 une instance `Configuration` en général au démarrage de l'application.

Une `Session` n'est pas coûteuse, et c'est un objet non-threadsafe qui ne devrait être utilisé qu'une seule fois pour une requête unique, une conversation, une unité de travail unique et devrait être relâché ensuite. Un objet `Session` ne tentera pas d'obtenir une `Connection`JBDC (ou une `Datasource`) si ce n'est pas nécessaire, par conséquent il ne consommera pas de ressource jusqu'à son utilisation.

Afin de compléter ce tableau, vous devez également penser aux transactions de base de données. Une transaction de base de données doit être aussi courte que possible afin de réduire les risques de contention de verrou dans la base de données. 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, ce n'est un bon design que de maintenir une transaction ouverte pendant la durée de reflexion de l'utilisateur, jusqu'à ce que l'unité de travail soit achevée.

Quelle est la portée d'une unité de travail? Est-ce qu'une `Session` unique de Hibernate peut avoir une durée de vie dépassant plusieurs transactions à la base de données, ou bien est-ce une relation un-à-un des portées? Quand faut-il ouvrir et fermer une `Session` et comment définir les démarcations de vos transactions à la base de données ?

### 13.1.1. Unité de travail

First, let's define a unit of work. A unit of work is a design pattern described by Martin Fowler as « [maintaining] a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. »[PoEAA] In other words, its a series of operations we wish to carry out against the database together. Basically, it is a transaction, though fulfilling a unit of work will often span multiple physical database transactions (see Section 13.1.2, « Longue conversation »). So really we are talking about a more abstract notion of a transaction. The term "business transaction" is also sometimes used in lieu of unit of work.

Tout d'abord n'utilisez pas l'anti-pattern session-par-operation et n'ouvrez et ne fermez pas la `Session` à chacun de vos accès simples à la base de données dans un même thread ! Par conséquent, le même raisonnement est applicable à la gestion des transactions dans la base de données. Dans une application, les appels à la base de données doivent être effectués selon une séquence ordonnée et sont regroupés en unités de travail atomiques. (Notez que l'utilisation d'une connexion auto-commit après chaque déclaration SQL est inutile dans une application. Ce mode de fonctionnement existe pour les applications émettant des commandes SQL ad-hoc à 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 déjà mentionné, 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 est de plus beaucoup plus facile à maintenir et plus extensible.

Le modèle d'utilisation le plus fréquemment rencontré dans des applications clients serveur multi-usagers est la session-par-requête. Dans ce modèle, la requête d'un client est envoyée au serveur (où la couche de persistance est implémentée via Hibernate), une nouvelle `Session` Hibernate est ouverte et toutes les opérations d'accès à la base de données sont exécutées à l'intérieur de celle-ci. Lorsque le travail est terminé (et que les réponses à envoyer au client ont été préparées), la session est flushée et fermée. Une seule transaction à la base de données peut être utilisée pour répondre à la requête du client. La transaction est démarrée et validée au même moment où la Session est ouverte et fermée. La relation entre la `Session` et la `Transaction` est donc un-à-un. Cette relation entre les deux est un-à-un et ce modèle permet de répondre parfaitement aux attentes de la grande majorité des applications.

Le défi réside dans l'implémentation. Hibernate fournit une fonction de gestion intégrée de la "session courante" pour simplifier ce pattern. Il vous suffit de démarrer une transaction lorsqu'une requête est traitée par le serveur, et la terminer avant que la réponse ne soit envoyée au client. Vous pouvez choisir la manière de l'effectuer, les solutions communes sont un `ServletFilter`, l'interception via AOP avec une coupe transverse (pointcut) sur les méthodes de type "service", ou un conteneur avec interception/proxy. Un conteneur EJB est un moyen standard d'implémenter ce genre d'acpect transverse comme la démarcation des transactions sur les EJB session, de manière déclarative avec CMT. Si vous décidez d'utiliser la démarcation programmatique des transactions, préferrez l'API Hibernate `Transaction` traitée plus tard dans ce chapitre, afin de faciliter l'utilisation et la portabilité du code.

Your application code can access a "current session" to process the request by calling `sessionFactory.getCurrentSession()`. You will always get a `Session` scoped to the current database transaction. This has to be configured for either resource-local or JTA environments, see Section 2.3, « Sessions contextuelles ».

Il est parfois utile d'étendre la portée 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. Étendre 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 EJB 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 .

### 13.1.2. Longue conversation

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.

Il apparaît donc évident qu'il faille utiliser plusieurs transactions BDD afin d'implémenter la conversation. Dans ce cas, maintenir l'isolation des processus d'affaire devient partiellement la responsabilité de la couche applicative. Ainsi, la durée de vie d'une conversation devrait englober celle d'une ou de plusieurs transactions de base de données. Celle-ci sera atomique seulement si l'écriture des données mises à jour est faite exclusivement par la dernière transaction BDD la composant. Toutes les autres sous transactions BD ne doivent faire que la lecture de données. Ceci est relativement facile à mettre en place, surtout avec l'utilisation de certaines fonctionnalités d'Hibernate :

- Versionnage Automatique - Hibernate peut gérer automatiquement les accès concurrents de manière optimiste et détecter si une modification concurrente s'est produite durant le temps de réflexion d'un usager. A vérifier en fin de 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.

- Session Longues (conversation) - Une `Session` Hibernate peut être déconnectée de la couche JDBC sous-jacente après que commit() ait été appelé sur une transaction à la base de données et reconnectée lors d'une nouvelle requête-client. Ce pattern s'appelle: session-per-conversation (Littéralement: session-par- conversation) et rend superflu le rattachement des objets. Le versionnage automatique est utilisé afin d'isoler les modifications concurrentes.

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.

### 13.1.3. L'identité des objets

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 :

Identité de database

  `foo.getId().equals( bar.getId() )`

Identité JVM

  `foo==bar`

Ainsi, pour des objets attachés à une `Session` particulière (c'est-à-dire dans la portée d'une instance de `Session`), ces deux notions d'identité sont équivalentes et l'identité JVM pour l'identité de la base de données sont garanties par Hibernate. Cependant, alors qu'une application peut accéder de manière concurrente au "même" objet métier (identité persistante) dans deux sessions différentes, les deux instances seront en fait "différentes" (en ce qui a trait à l'identité JVM). Les conflits sont résolus automatiquement par approche optimiste grâce au système de versionnage automatique au moment du flush/sauvegarde.

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 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. Par exemple, si vous mettez deux objets dans le même `Set` , ceux-ci pourraient avoir la même identité de base de données (c'est-à-dire 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 (en général avec des instances dégachées) 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.

### 13.1.4. Problèmes communs

Bien qu'il puisse y avoir quelques rares exceptions à cette règle, il est recommandé de ne jamais utiliser les anti-modèles session-par- utilisateur-session ou session-par-application . Notez que certains des problèmes suivants pourraient néanmoins survenir avec des modèles recommandés, assurez-vous de bien comprendre les implications de chacun des modèles avant de prendre une décision concernant votre design :

- 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. Si vous gardez votre `Session` Hibernate dans la `HttpSession` (le sujet sera traité ultérieurement), 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 exécutant dans des threads concurrents n'utilisent la même `Session`.

- Lorsque Hibernate lance une exception, le rollback de la transaction en cours dans la base de données, doit être effectué et la `Session` immédiatement fermée. (Nous approfondirons le sujet plus loin) Si votre `Session` est liée à l'application, il faut arrêter l'application. Le rollback de la transaction de base de données ne remettra pas les objets dans leur état du début de la transaction. Ainsi, cela signifie que l'état de la base de données et les objets d'affaires pourraient être désynchronisés d'avec les enregistrements. Généralement, cela ne cause pas de réel problème puisque la plupart des exceptions sont non traitables et de toutes façons, vous devez recommencer le processus après le rollback).

- The `Session` caches every object that is in a persistent state (watched and checked for dirty state by Hibernate). If you keep it open for a long time or simply load too much data, it will grow endlessly until you get an OutOfMemoryException. One solution is to call `clear()` and `evict()` to manage the `Session` cache, but you should consider a Stored Procedure if you need mass data operations. Some solutions are shown in Chapitre 15, Traitement par lot. Keeping a `Session` open for the duration of a user session also means a higher probability of stale data.

## 13.2. Démarcation des transactions de base de données

La démarcation des transactions de base de données (ou système) est toujours nécessaire. Aucune communication avec la base de données ne peut être effectuée à l'extérieur du cadre d'une transaction. (Il semble que ce concept soit mal compris par plusieurs développeurs trop habitués à utiliser le mode auto-commit.) Utilisez toujours la démarcation des des transactions, même pour des opérations en lecture seule. Certains niveaux d'isolation et certaines possibilités offertes par les bases de données permettent de l'éviter, il n'est jamais désavantageux de toujours explicitement indiquer les bornes de transaction. Il est certain qu'une transaction unique de base de données sera plus performante que de nombreuses petites transactions, même pour les opérations simples de lecture.

Une application utilisant Hibernate peut s'exécuter dans un environnement léger n'offrant pas la gestion automatique des transactions (application autonome, application web simple ou applications Swing) ou dans un environnement J2EE offrant des services de gestion automatiques des transactions JTA. Dans un environnement simple, Hibernate a généralement la responsabilité de la gestion de son propre pool de connexions à la base de données. Le développeur de l'application doit manuellement délimiter les transactions. En d'autres mots, il appartient au développeur de gérer les appels à `Transaction.begin()` , `Transaction.commit()` et `Transaction.rollback()`. Un environnement transactionnel J2EE (serveur d'application J2EE) doit offrir la gestion des transactions au niveau du conteneur J2EE. Les bornes de transaction peuvent normalement être définies de manière déclarative dans les descripteurs de déploiement d'EJB Session, par exemple. La gestion programmatique des transactions n'y est donc plus nécessaire.

Cependant, il est souvent préférable d'avoir une couche de persistance portable entre les environnements non gérés de ressources locales et les systèmes qui s'appuient sur JTA mais utilisent BMT à la place de CMT. Dans les deux cas, vous utiliserez la démarcation de transaction programmatique. Hibernate offre donc une API appelée `Transaction` qui sert d'enveloppe pour le système de transaction natif de l'environnement de déploiement. Il n'est pas obligatoire d'utiliser cette API, mais il est fortement conseillé de le faire, sauf lors de l'utilisation de CMT Session Bean.

Il existe quatre étapes distinctes lors de la fermeture d'une `Session` :

- flush de la session

- commit de la transaction

- fermeture de la session

- gestion des exceptions

La notion de "Flushing" a déjà été expliquée, nous abordons maintenant la démarcation des transactions et la gestion des exceptions dans les environnements gérés et non-gérés.

### 13.2.1. Environnement non gérés

Si la couche de persistance Hibernate s'exécute dans un environnement non géré, les connexions à la base de données seront généralement prises en charge par le mécanisme de pool d'Hibernate qui obtient les connexions. La gestion de la session et de la transaction se fera donc de la manière suivante :

    // Non-managed environment idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

You do not have to `flush()` the `Session` explicitly: the call to `commit()` automatically triggers the synchronization depending on the FlushMode for the session. A call to `close()` marks the end of a session. The main implication of `close()` is that the JDBC connection will be relinquished by the session. This Java code is portable and runs in both non-managed and JTA environments.

Une solution plus flexible est la gestion par contexte de la session courante intégrée, fournie par Hibernate que nous avons déjà rencontrée :

    // Non-managed environment idiom with getCurrentSession()

    try {

        factory.getCurrentSession().beginTransaction();

        // do some work

        ...

        factory.getCurrentSession().getTransaction().commit();

    }

    catch (RuntimeException e) {

        factory.getCurrentSession().getTransaction().rollback();

        throw e; // or display error message

    }

Vous ne verrez probablement jamais ces exemples de code dans les applications ; les exceptions fatales (exceptions du système) ne devraient être traitées que dans la couche la plus "haute". En d'autres termes, le code qui exécute les appels à Hibernate (à la couche de persistance) et le code qui gère les `RuntimeException` (qui ne peut généralement effectuer qu'un nettoyage et une sortie) sont dans des couches différentes. La gestion du contexte courant par Hibernate peut simplifier notablement ce design, puisqu'il vous suffit d'accéder à la `SessionFactory`. La gestion des exceptions est traitée plus loin dans ce chapitre.

Notez que vous devriez sélectionner `org.hibernate.transaction.JDBCTransactionFactory` (le défaut), pour le second exemple `"thread"` comme votre `hibernate.current_session_context_class`.

### 13.2.2. Utilisation de JTA

Si votre couche de persistance s'exécute dans un serveur d'applications (par exemple, derrière un EJB Session Bean), toutes les datasources utilisées par Hibernate feront automatiquement partie de transactions JTA globales. Vous pouvez également installer une implémentation autonome JTA et l'utiliser sans l'EJB.Hibernate propose deux stratégies pour réussir l'intégration JTA.

Si vous utilisez des transactions gérées par un EJB (bean managed transactions - BMT), Hibernate informera le serveur d'applications du début et de la fin des transactions si vous utilisez l'API `Transaction`. Ainsi, le code de gestion des transactions sera identique dans les environnements non gérés.

    // BMT idiom

    Session sess = factory.openSession();

    Transaction tx = null;

    try {

        tx = sess.beginTransaction();

        // do some work

        ...

        tx.commit();

    }

    catch (RuntimeException e) {

        if (tx != null) tx.rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Si vous souhaitez utiliser une `Session` couplée à la transaction, c'est à dire, utiliser la fonctionnalité `getCurrentSession()` pour la propagation facile du contexte, vous devez utiliser l'API JTA `UserTransaction` directement :

    // BMT idiom with getCurrentSession()

    try {

        UserTransaction tx = (UserTransaction)new InitialContext()

                                .lookup("java:comp/UserTransaction");

        tx.begin();

        // Do some work on Session bound to transaction

        factory.getCurrentSession().load(...);

        factory.getCurrentSession().persist(...);

        tx.commit();

    }

    catch (RuntimeException e) {

        tx.rollback();

        throw e; // or display error message

    }

Avec CMT, la démarcation des transactions est faite dans les descripteurs de déploiement des Beans Sessions et non de manière programmatique, par conséquent le code est réduit à :

    // CMT idiom

     Session sess = factory.getCurrentSession();

     // do some work

     ...

Dans un EJB CMT, le rollback aussi intervient automatiquement, puisqu'une `RuntimeException` non traitée et soulevée par une méthode d'un bean session indique au conteneur d'annuler la transaction globale. Ceci veut donc dire que vous n'avez pas à utiliser l'API `Transaction` de Hibernate dans CMT ou BMT et vous obtenez la propagation automatique de la session courante liée à la transaction.

Notez que le fichier de configuration Hibernate devrait contenir les valeurs `org.hibernate.transaction.JTATransactionFactory` dans un environnement BMT ou `org.hibernate.transaction.CMTTransactionFactory` dans un environnement CMT là où vous configurez votre fabrique de transaction Hibernate. N'oubliez pas non plus de spécifier le paramètre `org.hibernate.transaction.manager_lookup_class` . De plus, assurez vous de fixer votre `hibernate.current_session_context_class` soit à `"jta"` ou de ne pas le configurer (compatibilité avec les versions précédentes).

La méthode `getCurrentSession()` a un inconvénient dans les environnements JTA. Il y a une astuce qui est d'utiliser un mode de libération de connexion `after_statement` , qui est alors utilisé par défaut. Du à une étrange limitation de la spec JTA, il n'est pas possible à Hibernate de nettoyer automatiquement un `ScrollableResults` ouvert ou une instance d'`Iterator` retournés `scroll()` ou `iterate()`. Vous devez libérer le curseur base de données sous jacent ou invoquer `Hibernate.close(Iterator)` explicitement depuis un bloc `finally`. (Bien sur, la plupart des applications peuvent éviter d'utiliser `scroll()` ou `iterate()` dans un code JTA ou CMT.)

### 13.2.3. Gestion des exceptions

Si une `Session` lance une exception (incluant les exceptions du type `SQLException` ou d'un sous-type), vous devez immédiatement effectuer le rollback de la transaction, appeler `Session.close()` et relâcher les références sur l'objet `Session` . La `Session` contient des méthodes pouvant la mettre dans un état inutilisable. Vous devez considérer qu'aucune exception lancée par Hibernate n'est traitable comme recouvrable. Assurez-vous de fermer la session en appelant `close()` dans un bloc `finally` .

L'exception `HibernateException`, qui englobe la plupart des exceptions pouvant survenir dans la couche de persistance Hibernate, est une exception non vérifiée (Ceci n'était pas le cas dans des versions antérieures de Hibernate.) Nous pensons que nous ne devrions pas forcer un développeur à gérer une exception qu'il ne peut de toute façon pas traiter dans une couche technique. Dans la plupart des systèmes, les exceptions non vérifiées et les exceptions fatales sont gérées en amont du processus (dans les couches hautes) et un message d'erreur est alors affiché à l'usager (ou un traitement alternatif est invoqué.) Veuillez noter que Hibernate peut également lancer des exceptions non vérifiées d'un autre type que `HibernateException`. Celles-ci sont également non traitables et vous devez les traiter comme telles.

Hibernate englobe les `SQLException` s lancées lors des interactions directes avec la base de données dans des exceptions de type: `JDBCException`. En fait, Hibernate essaiera de convertir l'exception dans un sous-type plus significatif de `JDBCException`. L'exception `SQLException` sous-jacente est toujours disponible via la méthode `JDBCException.getCause()`. Hibernate convertit le `SQLExceptionConverter` en une sous-classe `JDBCException`, en utilisant le `SQLExceptionConverter` qui est rattaché à l'objet `SessionFactory`. Par défaut, le `SQLExceptionConverter` est défini par le dialecte configuré dans Hibernate. Toutefois, il est possible de fournir sa propre implémentation de l'interface. (Veuillez vous référer à la javadoc sur la classe `SQLExceptionConverterFactory` pour plus de détails. Les sous-types standard de `JDBCException` sont :

- `JDBCConnectionException` - indique une erreur de communication avec la couche JDBC sous-jacente.

- `SQLGrammarException` - indique un problème de grammaire ou de syntaxe avec la requête SQL envoyée.

- `ConstraintViolationException` - indique une violation de contrainte d'intégrité.

- `LockAcquisitionException` - indique une erreur de verrouillage lors de l'exécution de la requête.

- `GenericJDBCException` - indique une erreur générique ne correspondant à aucune autre catégorie.

### 13.2.4. Timeout de transaction

Une des caractéristiques extrêmement importante fournie dans les environnements gérés tels les EJB, est la gestion du timeout de transaction qui n'est jamais fournie pour le code non géré. La gestion des dépassements de temps de transaction vise à s'assurer qu'une transaction agissant incorrectement ne viendra pas bloquer indéfiniment les ressources de l'application et ne retourner aucune réponse à l'utilisateur. Hibernate ne peut fournir cette fonctionnalité dans un environnement transactionnel non-JTA. Par contre, Hibernate gère les opérations d'accès aux données en allouant un temps maximal aux requêtes pour s'exécuter. Ainsi, une requête créant de l'inter blocage ou retournant de très grandes quantités d'informations pourrait être interrompue. Dans un environnement géré, Hibernate peut déléguer au gestionnaire de transaction JTA, le soin de gérer les dépassements de temps. Cette fonctionnalité est abstraite par l'objet `Transaction`.

    Session sess = factory.openSession();

    try {

        //set transaction timeout to 3 seconds

        sess.getTransaction().setTimeout(3);

        sess.getTransaction().begin();

        // do some work

        ...

        sess.getTransaction().commit()

    }

    catch (RuntimeException e) {

        sess.getTransaction().rollback();

        throw e; // or display error message

    }

    finally {

        sess.close();

    }

Notez que `setTimeout()` ne peut pas être appelé d'un EJB CMT, puisque le timeout des transaction doit être spécifié de manière déclarative.

## 13.3. Contrôle de concurrence optimiste

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 mise à jour causant des conflits avec d'autres actualisations antérieures (et pour éviter la perte de mise à jour). Hibernate propose trois approches possibles 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 longues conversations, mais le versionnage peut également améliorer la qualité d'une application en prévenant la perte de mise à jour dans les transactions uniques de base de données.

### 13.3.1. Vérification du versionnage au niveau applicatif

Dans cet exemple d'implémentation utilisant peu les fonctionnalités de 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 base de données 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 parmi celles qui sont 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 mappage 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. Hibernate peut gérer automatiquement la vérification des versions d'objets en utilisant soit une `Session` longue, soit des instances détachées comme paradigme des conversations.

### 13.3.2. Les sessions longues et le versionnage automatique.

Dans ce scénario, une seule instance de `Session` et des objets persistants est utilisée pour toute la conversation, connue sous session-par-conversation. 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 base de données.

Notez 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, ou du rattachement 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. En général, vous configurerez`FlushMode.NEVER` sur une `Session` étendue, de façon que seul le dernier cycle de transaction de la base de données puissent persister toutes les modifications effectuées dans cette conversation. Par conséquent, cette dernière transaction inclura l'opération `flush()`, de même que `close()` la session pour finir la conversation.

Ce modèle 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 une `Session` que pour une seule conversation, car les objets risquent d'y être rapidement périmés.

## Remarque

Notez que des versions précédentes de Hibernate exigeaient une déconnexion explicite et une reconnexion d'une `Session`. Ces méthodes sont périmées, puisque commencer et terminer une transaction a le même effet.

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` dans un environnement 3 niveaux et évitez de la sérialiser et de la transférer à la couche de présentation (c'est-à-dire qu'il est préférable de ne pas la conserver dans la session `HttpSession` .)

Le modèle de session étendue, ou session-par-conversation, est plus difficile à implémenter avec la gestion automatique de contexte de session courante. À cet effet, vous devez fournir votre propre implémentation de`CurrentSessionContext`, pour des exemples consultez Hibernate Wiki.

### 13.3.3. Les objets détachés et le versionnage automatique

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

### 13.3.4. Personnaliser le versionnage automatique

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 si la propriété est dirty.

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 se fier à une colonne particulière dans une table. Afin de forcer la vérification de version dans un système sans en faire le mappage, mais en forçant une comparaison des états de tous les attributs d'une entité, vous pouvez utiliser l'attribut `optimistic- lock="all"` dans le mappage `<class>`. Veuillez noter que cette manière de gérer le versionnage ne peut être utilisée que si l'application utilise de longues sessions, lui permettant de comparer l'ancien état et le nouvel état d'une entité. L'utilisation d'un modèle `session-per-request-with-detached- objects` devient alors impossible.

Il peut être souhaitable de permettre les modifications concurrentes du moment que les modifications ne se chevauchent pas. En configurant la propriété à `optimistic-lock="dirty"` quand vous mappez le `<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 `UPDATE` par entité avec la clause `WHERE` appropriée pour vérifier la version et mettre à jour les informations. Si vous utilisez la persistance transitive pour propager l'évènement de rattachement à des entités associées, il est possible que Hibernate génère des commandes de mise à jour 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 détachés. Vous pouvez personnaliser ce comportement en indiquant `select-before- update="true"` dans l'élément de mappage `<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 de mise à jour de l'enregistrement.

## 13.4. Verrouillage pessimiste

Il n'est nécessaire de s'attarder à la stratégie de verrouillage des entités dans une application utilisant Hibernate. Il est généralement suffisant de définir le niveau d'isolation pour les connexions JDBC et de laisser ensuite la base de donnée effectuer son travail. Toutefois, certains utilisateurs avancés peuvent vouloir obtenir un verrouillage pessimiste exclusif sur un enregistrement, ou le ré-obtenir au lancement d'une nouvelle transaction.

Hibernate utilisera toujours le mécanisme de verrouillage de la base de données et ne verrouillera jamais les objets en mémoire.

La classe `LockMode` définit les différents niveaux de verrouillage pouvant être obtenus par Hibernate. Le verrouillage est obtenu par les mécanismes suivants :

- `LockMode.WRITE` est obtenu automatiquement quand Hibernate actualise ou insère un enregistrement.

- `LockMode.UPGRADE` peut être obtenu de manière explicite via la requête en utilisant `SELECT ... FOR UPDATE` sur une base de données supportant cette syntaxe.

- `LockMode.UPGRADE_NOWAIT` peut être obtenu de manière explicite en utilisant `SELECT ... FOR UPDATE NOWAIT` sur Oracle.

- `LockMode.READ` est obtenu automatiquement quand Hibernate lit des données dans un contexte d'isolation `Repeatable Read` ou `Serializable`. Peut être ré-obtenu explicitement via une requête d'utilisateur.

- `LockMode.NONE` représente l'absence de verrouillage. Tous les objets migrent vers ce mode à la fin d'une `Transaction` . Les objets associés à une session via un appel à `saveOrUpdate()` commencent également leur cycle de vie dans ce mode verrouillé.

Les requêtes explicites d'utilisateur sont exprimées d'une des manières suivantes :

- Un appel à `Session.load()`, en spécifiant un niveau verrouillage `LockMode` .

- Un appel à `Session.lock()`.

- Une appel à `Query.setLockMode()`.

Si `Session.load()` est appelé avec le paramètre de niveau de verrouillage `UPGRADE` ou `UPGRADE_NOWAIT` et que l'objet demandé n'est pas présent dans la session, celui-ci sera chargé à l'aide d'une requête `SELECT ... FOR UPDATE` . Si la méthode `load()` est appelée pour un objet déjà en session avec un verrouillage moindre que celui demandé, Hibernate appellera la méthode `lock()` pour cet objet.

`Session.lock()` effectue une vérification de version si le niveau de verrouillage est `READ` , `UPGRADE` ou `UPGRADE_NOWAIT` . Dans le cas des niveaux `UPGRADE` ou `UPGRADE_NOWAIT` , une requête `SELECT ... FOR UPDATE` sera utilisée.

Si une base de données ne supporte pas le niveau de verrouillage demandé, Hibernate utilisera un niveau alternatif convenable au lieu de lancer une exception. Ceci assurera la portabilité de vos applications.

## 13.5. Modes de libération de connexion

Le comportement original (2.x) de Hibernate pour la gestion des connexions JDBC était que la `Session` obtenait une connexion dès qu'elle en avait besoin et la libérait une fois la session fermée. Hibernate 3.x a introduit les modes de libération de connexion pour indiquer à la session comment gérer les transactions JDBC. Notez que la discussion suivante n'est pertinente que pour des connexions fournies par un `ConnectionProvider`, celles gérées par l'utilisateur dépassent l'objectif de cette discussion. Les différents modes de libération sont identifiés par les valeurs énumérées de `org.hibernate.ConnectionReleaseMode` :

- `ON_CLOSE` - est essentiellement le comportement passé décrit ci-dessus. La session Hibernate obtient une connexion lorsqu'elle en a besoin et la garde jusqu'à ce que la session se ferme.

- `AFTER_TRANSACTION` - indique de relâcher la connexion après qu'une `org.hibernate.Transaction` soit achevée.

- `AFTER_STATEMENT` (aussi appelé libération brutale) - indique de relâcher les connexions après chaque exécution d'un statement. Ce relâchement agressif est annulé si ce statement laisse des ressources associées à une session donnée ouvertes, actuellement ceci n'arrive que lors de l'utilisation de `org.hibernate.ScrollableResults`.

Le paramètre de configuration `hibernate.connection.release_mode` est utilisé pour spécifier quel mode de libération doit être utilisé. Les valeurs possibles sont :

- `auto` (valeur par défaut) - ce choix délègue le choix de libération à la méthode `org.hibernate.transaction.TransactionFactory.getDefaultReleaseMode()` Pour la JTATransactionFactory, elle retourne ConnectionReleaseMode.AFTER_STATEMENT; pour JDBCTransactionFactory, elle retourne ConnectionReleaseMode.AFTER_TRANSACTION. C'est rarement une bonne idée de changer ce comportement par défaut puisque les erreurs soulevées par ce paramétrage tend à indiquer la présence de bogues et/ou d'erreurs dans le code de l'utilisateur.

- `on_close` - indique d'utiliser ConnectionReleaseMode.ON_CLOSE. Ce paramétrage existe pour garantir la compatibilité avec les versions précédentes, mais ne devrait plus être utilisé.

- `after_transaction` - indique d'utiliser ConnectionReleaseMode.AFTER_TRANSACTION. Ne devrait pas être utilisé dans les environnements JTA. Notez aussi qu'avec ConnectionReleaseMode.AFTER_TRANSACTION, si une session est considérée comme étant en mode auto-commit les connexions seront relâchées comme si le mode était AFTER_STATEMENT.

- `after_statement` - indique d'utiliser ConnectionReleaseMode.AFTER_STATEMENT. De plus, le `ConnectionProvider` utilisé est consulté pour savoir s'il supporte ce paramétrage (`supportsAggressiveRelease()`). Si ce n'est pas le cas, le mode de libération est ré-initialisé à ConnectionReleaseMode.AFTER_TRANSACTION. Ce paramétrage n'est sûr que dans les environnements où il est possible d'obtenir à nouveau la même connexion JDBC à chaque fois que l'on fait un appel de `ConnectionProvider.getConnection()` ou dans les environnements auto-commit où il n'est pas important d'obtenir plusieurs fois la même connexion.

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 12. Read-only entities
- Niveau supérieur
- Sommaire
- SuivantChapitre 14. Intercepteurs et événements