La démarcation des transactions est importante dans le design d?une application. 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.) Même si 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 pour les opérations complexes comme 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 automatique 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
container 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 pas nécessaire. Même les appels
à Session.flush()
sont faits automatiquement.
Il peut être requis d'avoir une couche de persistance portable. 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 (EJB avec transactions
gérées automatiquement par le container EJB).
Il existe quatre étapes disctinctes lors de la fermeture d'une Session
flush de la session
commit de la transaction
Fermeture de la session (Close)
Gestion des exceptions
La synchronisation de bdd depuis la session (flush) a déjà été expliqué, nous nous attarderons maintenant à la démarcation des transactions et à la gestion des exceptions dans les environnements légers et les environnements J2EE.
Si la couche de persistance Hibernate s'exécute dans un environnement non managé, les connexions à la base de données seront généralement prises en charge par le mécanisme de pool d'Hibernate. 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(); }
Vous n'avez pas à invoquer flush()
explicitement sur la Session
- l'appel de commit()
déclenchera automatiquement la synchronisation (selon le Section 10.10, « Flush de la session » de la session. Un appel à close()
marque la fin de la session. La conséquence directe est que la connexion à la base de données sera relachée par la session.
Ce code est portable est fonctionne dans les environnements non managé ET les environnements JTA.
Une solution plus flexible est la gestion par contexte fourni par Hibernate que nous avons déjà rencontré:
// 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, puisque vous devez accéder à la gestion des exceptions de la
SessionFactory
, ce qui est décrit plus tard dans ce chapitre.
Notez que vous devriez sélectionner org.hibernate.transaction.JDBCTransactionFactory
(le défaut), pour le second exemple "thread"
comme hibernate.current_session_context_class
.
Si votre couche de persistance s'exécute dans un serveur d'application (par exemple, derrière un EJB Session Bean), toutes les datasource utilisées par Hibernate feront automatiquement partie de transactions JTA globales. Hibernate propose deux stratégies pour réussir cette intégration.
Si vous utilisez des transactions gérées par un EJB (bean managed transactions - BMT), Hibernate informera le serveur d'application
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 deux types d'environnements.
// 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(); }
Ou encore, avec la gestion automatique de contexte:
// 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 programmmatique, ceci réduit le code:
// CMT idiom Session sess = factory.getCurrentSession(); // do some work ...
Dans un EJB CMT même le rollback 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
d'Hibernate dans CMT.
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 transaction factory Hibernate. N'oubliez pas non plus de spécifier
le paramètre org.hibernate.transaction.manager_lookup_class
. De plus, assurez vous de fixez 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 environnement 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 pour Hibernate de nettoyer
et ferme 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'uiliser scroll()
ou iterate()
dans un code CMT.)
Si une Session
lance une exception (incluant les exceptions du type SQLException
ou d'un sous-type), vous devez immédiatement faire 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. Assurez-vous de fermer la session en faisant l'appel à 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 certaines versions antérieures de Hibernate.) Il est de notre avis 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 applications, 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 qu'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()
. Cette conversion est faite par un objet de type SQLExceptionConverter
, qui est rattaché à l'objet SessionFactory
. Par défaut, le SQLExceptionConverter
est associé au dialecte de BD 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'éxécution de la requête.
GenericJDBCException
- Indique une erreur générique JDBC d'une autre catégorie.
L'un des avantages fournis par les environnements transactionnels JTA (tels les containers EJB) est la gestion du timeout
de transaction. 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. 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'information
pourrait être interrompue. Dans un environnement transactionnel JTA, Hibernate peut déléguer au gestionnaire de transaction
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.