Chapitre 3. Configuration de la SessionFactory

Parce qu'Hibernate est conçu pour fonctionner dans différents environnements, il existe beaucoup de paramètres de configuration. Heureusement, la plupart ont des valeurs par défaut appropriées et la distribution d'Hibernate contient un exemple de fichier hibernate.properties qui montre les différentes options. Généralement, vous n'avez qu'à placer ce fichier dans votre classpath et à l'adapter.

3.1. Configuration par programmation

Une instance de net.sf.hibernate.cfg.Configuration représente un ensemble de mappings des classes Java d'une application vers la base de données SQL. La Configuration est utilisée pour construire un objet (immuable) SessionFactory. Les mappings sont constitués d'un ensemble de fichiers de mapping XML.

Vous pouvez obtenir une instance de Configuration en l'instanciant directement. Voici un exemple de configuration d'une source de données et d'un mapping composé de deux fichiers de configuration XML (qui se trouvent dans le classpath) :

Configuration cfg = new Configuration()
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml");

Une alternative (parfois meilleure) est de laisser Hibernate charger le fichier de mapping en utilisant getResourceAsStream() :

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class);

Hibernate va rechercher les fichiers de mappings /org/hibernate/auction/Item.hbm.xml et /org/hibernate/auction/Bid.hbm.xml dans le classpath. Cette approche élimine les noms de fichiers en dur.

Une Configuration permet également plusieurs valeurs optionnelles :

Properties props = new Properties();
...
Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class)
    .setProperties(props);

Une Configuration est sensée être un objet nécessaire pendant la phase de configuration et être libérée une fois la SessionFactory construite.

3.2. Obtenir une SessionFactory

Quand tous les mappings ont été parsés par la Configuration, l'application doit obtenir une fabrique d'instances de Session. Cette fabrique est supposée être partagée par tous les threads de l'application :

SessionFactory sessions = cfg.buildSessionFactory();

Cependant, Hibernate permet à votre application d'instancier plus d'une SessionFactory. C'est utile si vous utilisez plus d'une base de données.

3.3. Connexion JDBC fournie par l'utilisateur

Une SessionFactory peut ouvrir une Session en utilisant une connexion JDBC fournie par l'utilisateur. Ce choix de design permet à l'application d'obtenir les connexions JDBC de la façon qu'il lui plait :

java.sql.Connection conn = datasource.getConnection();
Session session = sessions.openSession(conn);

// do some data access work

L'application doit faire attention à ne pas ouvrir deux Sessions concurrentes en utilisant la même connexion !

3.4. Connexions JDBC fournie par Hibernate

Alternativement, vous pouvez laisser la SessionFactory ouvrir les connexions pour vous. La SessionFactory doit recevoir les propriétés de connexions JDBC de l'une des manières suivantes :

  1. Passer une instance de java.util.Properties à Configuration.setProperties().

  2. Placer hibernate.properties dans un répertoire racine du classpath

  3. Positionner les propriétés System en utilisant java -Dproperty=value.

  4. Inclure des éléments <property> dans le fichier hibernate.cfg.xml (voir plus loin).

Si vous suivez cette approche, ouvrir une Session est aussi simple que :

Session session = sessions.openSession(); // ouvre une nouvelle session
// faire quelques accès aux données, une connexion JDBC sera utilisée à la demande

Tous les noms et sémantiques des propriétés d'Hibernate sont définies dans la javadoc de la classe net.sf.hibernate.cfg.Environment. Nous allons décrire les paramètres les plus importants pour une connexion JDBC.

Hibernate obtiendra des connexions (et les mettra dans un pool) en utilisant java.sql.DriverManager si vous positionner les paramètres de la manière suivante :

Tableau 3.1. Propriétés JDBC d'Hibernate

Nom de la propriétéFonction
hibernate.connection.driver_classClasse du driver jdbc
hibernate.connection.urlURL jdbc
hibernate.connection.usernameutilisateur de la base de données
hibernate.connection.passwordmot de passe de la base de données
hibernate.connection.pool_sizenombre maximum de connexions dans le pool

L'algorithme natif de pool de connexions d'Hibernate est plutôt rudimentaire. Il a été fait dans le but de vous aider à démarrer et n'est pas prévu pour un système en production ou même pour un test de peformance. Utiliser un pool tiers pour de meilleures performances et une meilleure stabilité : remplacer la propriété hibernate.connection.pool_size avec les propriétés spécifique au pool de connexions que vous avez choisi.

C3P0 est un pool de connexions JDBC open source distribué avec Hibernate dans le répertoire lib. Hibernate utilisera le provider intégré C3P0ConnectionProvider pour le pool de connexions si vous positionnez les propriétés hibernate.c3p0.*. Il y a également un support intégré pour Apache DBCP et Proxool. Vous devez positionner les propriétés hibernate.dbcp.* (propriétés du pool de connexions DBCP) pour activer le DBCPConnectionProvider. Le cache des Prepared Statement est activé (fortement recommandé) si hibernate.dbcp.ps.* (propriétés du cache de statement de DBCP) sont positionnées. Merci de vous référer à la documentation de apache commons-pool pour l'utilisation et la compréhension de ces propriétés. Vous devez positionner les propriétés hibernate.proxool.* si vous voulez utiliser Proxool.

Voici un exemple utilisant C3P0:

hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statement=50
hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect

Dans le cadre de l'utilisation au sein d'un serveur d'applications, Hibernate peut obtenir les connexions à partir d'une javax.sql.Datasource enregistrée dans le JNDI. Positionner les propriétés suivantes :

Tableau 3.2. Propriété d'une Datasource Hibernate

Nom d'une propriétéfonction
hibernate.connection.datasourceNom JNDI de la datasource
hibernate.jndi.urlURL du fournisseur JNDI (optionnelle)
hibernate.jndi.classClasse de l'InitialContextFactory du JNDI (optionnelle)
hibernate.connection.usernameutilisateur de la base de données (optionnelle)
hibernate.connection.passwordmot de passe de la base de données (optionnelle)

voici un exemple utilisant les datasources JNDI fournies par un serveur d'applications :

hibernate.connection.datasource = java:/comp/env/jdbc/MyDB
hibernate.transaction.factory_class = \
    net.sf.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
    net.sf.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = \
    net.sf.hibernate.dialect.PostgreSQLDialect

Les connexions JDBC obtenues à partir d'une datasource JNDI participeront automatiquement aux transactions gérées par le conteneur du serveur d'applications.

Des propriétés supplémentaires de connexion peuvent être passées en préfixant le nom de la propriété par "hibernate.connnection". Par exemple, vous pouvez spécifier un jeu de caractères en utilisant hibernate.connnection.charSet.

Vous pouvez fournir votre propre stratégie d'obtention des connexions JDBC en implémentant l'interface net.sf.hibernate.connection.ConnectionProvider. Vous pouvez sélectionner une implémentation spécifique en positionnant hibernate.connection.provider_class.

3.5. Propriétés de configuration optionnelles

Il y a un certain nombre d'autres propriétés qui contrôlent le fonctionnement d'Hibernate à l'exécution. Toutes sont optionnelles et ont comme valeurs par défaut des valeurs "raisonnables" pour un fonctionnement nominal.

Les propriétés de niveau System ne peuvent être positionnées que via la ligne de commande (java -Dproperty=value) ou être définies dans hibernate.properties. Elle ne peuvent l'être dans une instance de Properties passée à la Configuration.

Tableau 3.3. Propriétés de configuration d'Hibernate

Nom de la propriétéFonction
hibernate.dialect Le nom de la classe du Dialect Hibernate - active l'utilisation de certaines fonctionalités spécifiques à la plateforme.

ex. nom.complet.de.ma.classe.de.Dialect

hibernate.default_schema Positionne dans le SQL généré un schéma/tablespace par défaut pour les noms de table ne l'ayant pas surchargé.

ex. MON_SCHEMA

hibernate.session_factory_name La SessionFactory sera automatiquement liée à ce nom dans le JNDI après sa création.

ex. jndi/nom/hierarchique

hibernate.use_outer_join Active le chargement via les jointures ouvertes. Dépréciée, utiliser max_fetch_depth.

ex. true | false

hibernate.max_fetch_depth Définit la profondeur maximale d'un arbre de chargement par jointures ouvertes pour les associations à cardinalité unitaire (un-à-un, plusieurs-à-un). Un 0 désactive le chargement par jointure ouverte.

ex. valeurs recommandées entre 0 et 3

hibernate.jdbc.fetch_size Une valeur non nulle détermine la taille de chargement des statements JDBC (appelle Statement.setFetchSize()).
hibernate.jdbc.batch_size Une valeur non nulle active l'utilisation par Hibernate des mises à jour par batch de JDBC2.

ex. les valeurs recommandées entre 5 et 30

hibernate.jdbc.batch_versioned_data Paramétrez cette propriété à true si votre pilote JDBC retourne des row counts corrects depuis executeBatch() (il est souvent approprié d'activer cette option). Hibernate utilisera alors le "batched DML" pour versionner automatiquement les données. Par défaut = false.

eg. true | false

hibernate.jdbc.use_scrollable_resultset Active l'utilisation par Hibernate des resultsets scrollables de JDBC2. Cette propriété est seulement nécessaire lorsque l'on utilise une connexion JDBC fournie par l'utilisateur. Autrement, Hibernate utilise les métadonnées de la connexion.

ex. true | false

hibernate.jdbc.use_streams_for_binary Utilise des flux lorsque l'on écrit/lit des types binary ou serializable vers et à partir de JDBC (propriété de niveau système).

ex. true | false

hibernate.jdbc.use_get_generated_keys Active l'utilisation de PreparedStatement.getGeneratedKeys() de JDBC3 pour récupérer nativement les clés générées après insertion. Nécessite un pilote JDBC3+, le mettre à false si votre pilote a des problèmes avec les générateurs d'identifiant Hibernate. Par défaut, essaie de déterminer les possibilités du pilote en utilisant les meta données de connexion.

eg. true|false

hibernate.cglib.use_reflection_optimizer Active l'utilisation de CGLIB à la place de la réflexion à l'exécution (Propriété de niveau système, la valeur par défaut étant d'utiliser CGLIB lorsque c'est possible). La réflexion est parfois utile en cas de problème.

ex. true | false

hibernate.jndi.<propertyName> Passe la propriété propertyName au JNDI InitialContextFactory.
hibernate.connection.isolation Positionne le niveau de transaction JDBC. Merci de vous référer à java.sql.Connection pour le détail des valeurs mais sachez que toutes les bases de données ne supportent pas tous les niveaux d'isolation.

ex. 1, 2, 4, 8

hibernate.connection.<propertyName> Passe la propriété JDBC propertyName au DriverManager.getConnection().
hibernate.connection.provider_class Le nom de classe d'un ConnectionProvider spécifique.

ex. nom.de.classe.du.ConnectionProvider

hibernate.cache.provider_class Le nom de classe d'un CacheProvider spécifique.

ex. nom.de.classe.du.CacheProvider

hibernate.cache.use_minimal_puts Optimise le cache de second niveau en minimisant les écritures, au prix de plus de lectures (utile pour les caches en cluster).

ex. true|false

hibernate.cache.use_query_cache Activer le cache de requête, les requêtes individuelles doivent tout de même être déclarées comme mettable en cache.

ex. true|false

hibernate.cache.query_cache_factory Le nom de classe d'une interface QueryCache , par défaut = built-in StandardQueryCache.

eg. nom.de.la.classe.de.QueryCache

hibernate.cache.region_prefix Un préfixe à utiliser pour le nom des régions du cache de second niveau.

ex. prefix

hibernate.transaction.factory_class Le nom de classe d'une TransactionFactory qui sera utilisée par l'API Transaction d'Hibernate (la valeur par défaut est JDBCTransactionFactory).

ex. nom.de.classe.d.une.TransactionFactory

jta.UserTransaction Le nom JNDI utilisé par la JTATransactionFactory pour obtenir la UserTransaction JTA du serveur d'applications.

eg. jndi/nom/compose

hibernate.transaction.manager_lookup_class Le nom de la classe du TransactionManagerLookup - requis lorsque le cache de niveau JVM est activé dans un environnement JTA.

ex. nom.de.classe.du.TransactionManagerLookup

hibernate.query.substitutions Lien entre les tokens de requêtes Hibernate et les tokens SQL (les tokens peuvent être des fonctions ou des noms littéraux par exemple).

ex. hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC

hibernate.show_sql Ecrit les ordres SQL dans la console.

ex. true | false

hibernate.hbm2ddl.auto Exporte le schéma DDL vers la base de données automatiquement lorsque la SessionFactory est créée. La valeur create-drop permet de supprimer le schéma de base de données lorsque la SessionFactory est fermée explicitement.

ex. update | create | create-drop

3.5.1. Dialectes SQL

Vous devriez toujours positionner la propriété hibernate.dialect à la sous-classe appropriée à votre base de données. Ce n'est pas strictement obligatoire à moins de vouloir utiliser la génération de clé primaire native ou par sequence ou de vouloir utiliser le mécanisme de lock pessimiste (ex. via Session.lock() ou Query.setLockMode()). Cependant, si vous spécifiez un dialecte, Hibernate utilisera des valeurs adaptées pour certaines autres propriétés listées ci-dessus, vous évitant l'effort de le faire à la main.

Tableau 3.4. Dialectes SQL d'Hibernate (hibernate.dialect)

SGBDDialecte
DB2net.sf.hibernate.dialect.DB2Dialect
DB2 AS/400net.sf.hibernate.dialect.DB2400Dialect
DB2 OS390net.sf.hibernate.dialect.DB2390Dialect
PostgreSQLnet.sf.hibernate.dialect.PostgreSQLDialect
MySQLnet.sf.hibernate.dialect.MySQLDialect
SAP DBnet.sf.hibernate.dialect.SAPDBDialect
Oracle (toutes versions)net.sf.hibernate.dialect.OracleDialect
Oracle 9/10gnet.sf.hibernate.dialect.Oracle9Dialect
Sybasenet.sf.hibernate.dialect.SybaseDialect
Sybase Anywherenet.sf.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Servernet.sf.hibernate.dialect.SQLServerDialect
SAP DBnet.sf.hibernate.dialect.SAPDBDialect
Informixnet.sf.hibernate.dialect.InformixDialect
HypersonicSQLnet.sf.hibernate.dialect.HSQLDialect
Ingresnet.sf.hibernate.dialect.IngresDialect
Progressnet.sf.hibernate.dialect.ProgressDialect
Mckoi SQLnet.sf.hibernate.dialect.MckoiDialect
Interbasenet.sf.hibernate.dialect.InterbaseDialect
Pointbasenet.sf.hibernate.dialect.PointbaseDialect
FrontBasenet.sf.hibernate.dialect.FrontbaseDialect
Firebirdnet.sf.hibernate.dialect.FirebirdDialect

3.5.2. Chargement par Jointure Ouverte

Si votre base de données supporte les outer joins de type ANSI ou Oracle, le chargement par jointure ouverte devrait améliorer les performances en limitant le nombre d'aller-retour avec la base de données (la base de données effectuant donc potentiellement plus de travail). Le chargement par jointure ouverte permet à un graphe connecté d'objets par une relation plusieurs-à-un, un-à-plusieurs ou un-à-un d'être chargé en un seul SELECT SQL.

Par défaut, le graphe chargé lorsqu'un objet est demandé, finit aux objets feuilles, aux collections, aux objets avec proxy ou lorsqu'une circularité apparaît.

Le chargement peut être activé ou désactivé (valeur par défaut) pour une association particulière, en positionant l'attribut outer-join dans le mapping XML.

Le chargement par jointure ouverte peut être désactivé de manière globale en positionant la propriété hibernate.max_fetch_depth à 0. Une valeur de 1 ou plus permet les jointures ouvertes pour toutes les associations un-à-un et plusieurs-à-un qui sont, par défaut, positionnées à la valeur de jointure outerte auto. Cependant, les associations un-à-plusieurs et les collections ne sont jamais chargées en utilisant une jonture ouverte, à moins de le déclarer de façon explicite pour chaque association. Cette fonctionalité peut être surchargée à l'exécution dans les requêtes Hibernate.

3.5.3. Flux binaires

Oracle limite la taille d'un tableau de byte qui peuvent être passées à et vers son pilote JDBC. Si vous souhaitez utiliser des instances larges de type binary ou serializable, vous devez activer la propriété hibernate.jdbc.use_streams_for_binary. C'est une fonctionalité de niveau JVM uniquement.

3.5.4. CacheProvider spécifique

Vous pouvez intégrer un cache de second niveau de type JVM (ou cluster) en implémentant l'interface net.sf.hibernate.cache.CacheProvider. Vous pouvez sélectionner l'implémentation spécifique en positionnant hibernate.cache.provider_class.

3.5.5. Configuration de la stratégie transactionnelle

Si vous souhaitez utiliser l'API d'Hibernate Transaction, vous devez spécifier une classe factory d'instances de Transaction en positionnant la propriété hibernate.transaction.factory_class. L'API Transaction masque le mécanisme de transaction sous-jacent et permet au code utilisant Hibernate de tourner dans des environnements managés et non-managés sans le moindre changement.

Il existe deux choix standards (fournis) :

net.sf.hibernate.transaction.JDBCTransactionFactory

délègue aux transactions de la base de données (JDBC). Valeur par défaut.

net.sf.hibernate.transaction.JTATransactionFactory

délègue à JTA (si une transaction existant est en cours, la Session exécute son travail dans ce contexte ; sinon, une nouvelle transaction est démarrée).

Vous pouvez également définir votre propre stratégie transactionnelle (pour un service de transaction CORBA par exemple).

Si vous voulez utiliser un cache de niveau JVM pour des données muables dans un environnement JTA, vous devez spécifier une stratégie d'obtention du TransactionManager JTA. En effet, cet accès n'est pas standardisé par la norme J2EE :

Tableau 3.5. TransactionManagers JTA

Factory de TransactionServeur d'application
net.sf.hibernate.transaction.JBossTransactionManagerLookupJBoss
net.sf.hibernate.transaction.WeblogicTransactionManagerLookupWeblogic
net.sf.hibernate.transaction.WebSphereTransactionManagerLookupWebSphere
net.sf.hibernate.transaction.OrionTransactionManagerLookupOrion
net.sf.hibernate.transaction.ResinTransactionManagerLookupResin
net.sf.hibernate.transaction.JOTMTransactionManagerLookupJOTM
net.sf.hibernate.transaction.JOnASTransactionManagerLookupJOnAS
net.sf.hibernate.transaction.JRun4TransactionManagerLookupJRun4
net.sf.hibernate.transaction.BESTransactionManagerLookupBorland ES

3.5.6. SessionFactory associée au JNDI

Une SessionFactory Hibernate associée au JNDI peut simplifier l'accès à la fabrique et donc la création de nouvelles Sessions.

Si vous désirez associer la SessionFactory à un nom JNDI, spécifiez un nom (ex. java:comp/env/hibernate/SessionFactory) en utilisant la propriété hibernate.session_factory_name. Si cette propriété est omise, la SessionFactory ne sera pas associée au JNDI (c'est particulièrement pratique dans les environnements ayant une implémentation de JNDI en lecture seule, comme c'est le cas pour Tomcat).

Lorsqu'il associe la SessionFactory au JNDI, Hibernate utilisera les valeurs de hibernate.jndi.url, hibernate.jndi.class pour instancier un contexte d'initialisation. S'ils ne sont pas spécifiés, l'InitialContext par défaut sera utilisé.

Si vous décidez d'utiliser JNDI, un EJB ou toute autre classe utilitaire pourra obtenir la SessionFactory en faisant un accès au JNDI.

3.5.7. Substitution dans le langage de requêtage

Vous pouvez définir de nouveaux tokens dans les requêtes Hibernate en utilisant la propriété hibernate.query.substitutions. Par exemple :

hibernate.query.substitutions vrai=1, faux=0

remplacerait les tokens vrai et faux par des entiers dans le SQL généré.

hibernate.query.substitutions toLowercase=LOWER

permettrait de renommer la fonction SQL LOWER en toLowercase

3.6. Logguer

Hibernate loggue divers évènements en utilisant Apache commons-logging.

Le service commons-logging délèguera directement à Apache Log4j (si vous incluez log4j.jar dans votre classpath) ou le système de log du JDK 1.4 (si vous tournez sous le JDK 1.4 et supérieur). Vous pouvez télécharger Log4j à partir de http://jakarta.apache.org. Pour utiliser Log4j, vous devrez placer dans votre classpath un fichier log4j.properties. Un exemple de fichier est distribué avec Hibernate dans le répertoire src/.

Nous vous recommandons fortement de vous familiariser avec les messages de logs d'Hibernate. Beaucoup de soins a été apporté pour donner le plus de détails possibles sans les rendre illisibles. C'est un outil essentiel en cas de soucis. De même, n'oubliez pas d'activer les logs SQL comme décrit précédemment hibernate.show_sql, c'est la première étape pour regarder les problèmes de performance.

3.7. Implémenter une NamingStrategy

L'interface net.sf.hibernate.cfg.NamingStrategy vous permet de spécifier une "stratégie de nommage" des objets et éléments de la base de données.

Vous pouvez fournir des règles pour automatiquement générer les identifiants de base de données à partir des identifiants Java, ou transformer une colonne ou table "logique" donnée dans le fichier de mapping en une colonne ou table "physique". Cette fonctionnalité aide à réduire la verbosité de documents de mapping, en éliminant le bruit répétitif (les préfixes TBL_ par exemple). La stratégie par défaut utilisée par Hibernate est minimale.

Vous pouvez définir une stratégie différente en appelant Configuration.setNamingStrategy() avant d'ajouter des mappings :

SessionFactory sf = new Configuration()
    .setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml")
    .buildSessionFactory();

net.sf.hibernate.cfg.ImprovedNamingStrategy est une stratégie fournie qui peut être utile comme point de départ de quelques applications.

3.8. Fichier de configuration XML

Une approche alternative est de spécifier toute la configuration dans un fichier nommé hibernate.cfg.xml. Ce fichier peut être utilisé à la place du fichier hibernate.properties, voire même peut servir à surcharger les propriétés si les deux fichiers sont présents.

Le fichier de configuration XML doit par défaut se placer à la racine du CLASSPATH. En voici un exemple :

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 2.0//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <!-- une instance de SessionFactory accessible par son nom jndi -->
    <session-factory
        name="java:comp/env/hibernate/SessionFactory">

        <!-- propriétés -->
        <property name="connection.datasource">ma/premiere/datasource</property>
        <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">false</property>
        <property name="use_outer_join">true</property>
        <property name="transaction.factory_class">
            net.sf.hibernate.transaction.JTATransactionFactory
        </property>
        <property name="jta.UserTransaction">java:comp/UserTransaction</property>

        <!-- mapping files -->
        <mapping resource="org/hibernate/auction/Item.hbm.xml"/>
        <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

Configurer Hibernate devient donc aussi simple que ceci :

SessionFactory sf = new Configuration().configure().buildSessionFactory();

Vous pouvez utiliser une fichier de configuration XML de nom différent en utilisant

SessionFactory sf = new Configuration()
    .configure("/my/package/catdb.cfg.xml")
    .buildSessionFactory();