Hibernate

Présentation

1. Les besoins

Hibernate est un logiciel écrit sous la responsabilité de Gavin King, qui fait entre autre partie de l'équipe de développement de JBOSS.
L'ensemble des données nécessaires au fonctionnement de l'application sont sauvegardées dans une base de données. La manipulation des données peut se faire de différentes manières : Par l'accès directement à la base en écrivant les requêtes SQL adéquates. Utiliser un outil d'ORM (object Relationnal Mapping) permettant de manipuler facilement les données et d'assurer leur persistance. Il en existe plusieurs. Hibernate, présenté dans ce document, est l'un des plus utilisés.
Pourquoi ajouter une couche entre l'application et la base de données ? L'objectif est de réduire le temps de développement de l'application en éliminant une grande partie du code SQL à écrire pour interagir avec la base de données et en encapsulant le code SQL résiduel. Les développeurs manipulent les classes dont les données doivent être persistantes comme des classes Java normales. Seules une initialisation correcte d'hibernate doit être effectuée, et quelques règles respectées lors de l'écriture et de la manipulation des classes persistantes.

2. Comparaison

Il existe d'autres outils d'ORM permettant d'assurer la persistance de données, comme JDO et TopLink. Pour l'outil d'ORM JDO, il semble qu'il y ai quelques problèmes de compatibilité avec les EJB V3. Voila ce qu'il est écrit sur :
http://stessy.developpez.com/j2ee/hibernate/?page=page_3#L3.3.5.2 :
« Pour information, il semble que la norme EJB V3, fasse un virage important sur les Session et Entity Beans qui deviendraient des POJO ( suppression des homes interfaces, des fichiers de dexriptions de déploiement ...). La compatibilité ascendante [avec Hibernate] serait assurée. Les tests de EJB V3 serait fait à partir de deux logiciels de mapping O/R ( Hibernate et TopLink) mais pas JDO !. Gavin King [Responsable d'hibernate] fait partie du groupe d'expert EJB V3... »

3. Architecture

Voici une vue (très) haut niveau de l'architecture d'Hibernate :

archi1

Ce diagramme montre Hibernate utilisant une base de données et des données de configuration pour fournir un service de persistance (et des objets persistants) à l'application.
L'architecture la plus complète abstrait l'application des APIs JDBC/JTA sous-jacentes et laisse Hibernate s'occuper des détails.

archi2



Voici quelques descriptions des différents éléments

SessionFactory (net.sf.hibernate.SessionFactory)
Un cache threadsafe (immuable) des mappings vers une base de données.

Session (net.sf.hibernate.Session)
Un objet mono-threadé, à durée de vie courte, qui représente une conversation entre l'application et l'entrepôt de persistance. Elle encapsule une connexion JDBC.

Objets et Collections persistants
Objets mono-threadés à vie courte contenant l'état de persistance et la fonction métier. Ceux-ci sont en général les objets de type JavaBean (ou POJOs) ; la seule particularité est qu'ils sont associés avec une (et une seule) Session. Dès que la Session est fermée, ils seront détachés et libre d'être utilisés par n'importe laquelle des couches de l'application.

Objets et collections transients
Instances de classes persistantes qui ne sont actuellement pas associées à une Session. Elles ont pu être instanciées par l'application et ne pas avoir été persistées ou elle ont pu être instanciées par une Session fermée.

Détail des fonctions

1. Configuration d'hibernate

Hibernate permet de manipuler facilement les objets persistants mais demande une configuration rigoureuse.
Import des librairies nécessaires à l'utilisation d'hibernate
Pour pouvoir utiliser Hibernate, les librairies fournies dans l'archive d'hibernate (HIBERNATE_HOME/hibernate2.jar et HIBERNATE_HOME/lib/*.jar) ainsi que le driver de la base de données doivent être ajoutés dans les chemins de compilation et d'exécution du projet.

Placement des fichiers de configuration
Les fichiers situés sous HIBERNATE_HOME/etc/ * doivent être placés au niveau du contexte d'exécution de l'application, ou bien configurer dans hibernate.cfg.xml.

Configuration de la connexion à la base de données
Hibernate fonctionne avec un grand nombre de bases de données, notamment PostgresSQL, MySQL, DB2, Sybase, Interbase, SAP, Ingres... La configuration de la connexion à la base s'effectue avec le fichier hibernate.properties. Voici un exemple de configuration avec une base postgresSql en local :



## PostgreSQL
hibernate.dialect net.sf.hibernate.dialect.PostgreSQLDialect
	// dialecte utilisé
hibernate.connection.driver_class org.postgresql.Driver
	// classe du driver
hibernate.connection.url jdbc:postgresql:essai
	// adresse et nom de la base de données
hibernate.connection.username mat
	// nom de l'utilisateur
hibernate.connection.password 
	// mot de passe
hibernate.connexion.pool_size 10 
	// utilisation du pool de connexion de base
## print all generated SQL to the console
hibernate.show_sql true
	// option de visualisation des requêtes SQL lancées.

Le pool de connexion par défaut utilisé par hibernate n'est pas utilisable en production. Il ne doit être utilisé que pour les tests. On peut utiliser d'autres pools déjà intégré à hibernate : C3P0, Proxool et Apache DBCD.

2. Construction et mapping d'une classe persistante

Une classe que l'on veut rendre persistantes doit obéir à une condition : elle doit être un POJO (Plain Ordinary Java Object). Ce qui signifie que :
-Tous les attributs doivent posséder leur getter et leur setter.
-La classe doit implémenter le constructeur par défaut.
-L'identifiant de la classe doit être de type : type primitif, wrapper de type primitif, String ou java.util.Date
Il y a deux façons de décrire comment mapper les classes persistantes, soit dans le code java, en utilisant XDoclet, soit dans un nouveau fichier portant le même nom que la classe mappée avec comme extension *.hbm.xml.
Exemple : Mapping de la classe Personne dans un fichier séparé.

personne



<xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
 <class name="utilisateur1.Personne" table="utilisateurs">
 	<id name="idUser" type="long" unsaved-value="null" column="iduser">
		<generator class="assigned"/>
	</id>
	<property name="nom" type="string"/>
	<property name="prenom" type="string"/>
	<property name="password" type="string"/>
</class>
</hibernate-mapping>

On remarque dans le fichier la définition de la classe Personne, correspondant à la table utilisateurs. Puis on définit l'identifiant de la classe. Dans l'exemple, l'identifiant est l'attribut de la classe idUser et correspond à la colonne iduser. Les <property> correspondent aux attributs de la personne, et sont enregistrés par défaut dans les colonnes de la table du même nom. Il est possible de définir comment générer l'identifiant de la classe. Dans l'exemple, le générateur de l'identifiant est assigned, ce qui veut dire que l'utilisateur de la classe fournira lui même un identifiant. Il existe de nombreux type de générateur : increment, identity, sequence, hilo, seqhilo, uuid.hex, uuid.string, native, assigned, foreign. Le générateur « sequence » est le plus approprié avec l'utilisation d'une base PostGresSQL pour générer des identifiants automatiques.

3. Génération de la base de données

Il est possible de générer automatiquement le schéma (code SQL) de la base de données à partir des fichiers de mapping des classes.
Commande de génération de la base de données :


java  net.sf.hibernate.tool.hbm2ddl.SchemaExport  --text --format --output=ex2bis.ddl delimeter=x *.hbm.xml

Mapping des relations entre les tables

L'application « BOSS » comporte de nombreuses classes à mapper, faisant référence à une ou plusieurs tables. Ces classes ont des relations de type différents, qu'il faut pouvoir décrire dans les fichiers de mappings.

1. Relation <one to one>

personne

Exemple : une personne peut être associé à son statut d'employé par une relation Une des possibilités de définition de la relation <one to one> dans les fichiers de mapping s'écrit comme présenté ci-dessous :

Employé :
<one-to-one name="person" class="Person"/>

Personne :
<one-to-one name="employee" class="Employee" constrained="true"/>
Les deux enregistrements associés auront la même clé primaire.

2. Relation d'héritage

personne

Exemple : une personne peut être associée à son statut d'employé ou d'externe par une relation d'héritage. Cette technique peut se décompose en trois cas :
-table par hiérachie : création d'une seule table. (personne avec 2 colonnes en plus société et codeInterne)
-table par classe fille : création de 3 tables (personne - employé - externe)
-table par classe concrète : création de deux tables (employé - externe)
Dans le premier cas, le fichier de mapping s'écrit :



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<!-- Strategie table-per-class hierarchy mapping -->
<hibernate-mapping package="utilisateur4">
	 <class name="Personne" table="PERSONNES" discriminator-value="P">
	 	<id name="idPers" column="idPers" type="long">
	 		<generator class="sequence"/>
        </id>
        <discriminator column="sousclasse" type="character"/>
		<property name="nom" column="nom" type="string"/>
		<property name="prenom" column="prenom" type="string"/>
        <set name="identifications" inverse="true" cascade="all-delete-orphan" >
		 	<key column="pers"/>
            <one-to-many class="utilisateur4.Identification" />
		</set>
		<subclass name="Employe" discriminator-value="E">
			<property name="codeInterne" column="codeInterne" type="string"/>
		</subclass>
		<subclass name="Externe" discriminator-value="X">
			<property name="societe" column="societe" type="string"/>
		</subclass>
	</class>
</hibernate-mapping>

3. Relation <many to one>

personne

Exemple : une personne peut être associée à plusieurs identifications dans le cadre d'un logiciel gérant des profils. La classe Personne a un nouvel attribut , correspondant à une collection d'objet Identification, et la classe Identification à un nouveau champ correspondant à une personne. Le fichier de mapping de la classe Personne s'écrit :


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<!-- Strategie table-per-class hierarchy mapping -->
<hibernate-mapping package="utilisateur3">
	 <class name="Personne" table="PERSONNES" discriminator-value="P">
	 	<id name="idPers" column="idPers" type="long">
	 		<generator class="sequence"/>
        </id>
        <discriminator column="sousclasse" type="character"/>
		<property name="nom" column="nom" type="string"/>
		<property name="prenom" column="prenom" type="string"/>
        <set name="identifications" inverse="true" cascade="all-delete-orphan">
		 <key column="pers"/>
            <one-to-many class="utilisateur3.Identification" />
	   </set>
	</class>
</hibernate-mapping>

La balise <set> permet d'identifier la collection qui contiendra les identifications. La clause cascade="all-delete-orphan" permet d'effacer les identifications correspondant à une personne lors de la suppression de la personne. Le fichier de mapping de la classe Identification s'écrit :


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="utilisateur3">
	<class name="Identification" table="UTILISATEUR2" >
		<id name="idUser" column="idUser" type="long">
			<generator class="sequence" />
		</id>
		<property name="login" column="login" type="string"/>
		<property name="password" column="password" type="string"/>
		<many-to-one name="pers" class="utilisateur3.Personne" not-null="true"/>
	</class>
</hibernate-mapping>

4. Relation <many to many>

personne

Exemple : une personne peut être associée à plusieurs rôles dans une entreprise et un rôle peut être affecté à plusieurs personnes.
La classe Personne a un nouvel attribut, correspondant à une collection d'objets rôle, et la classe rôle a une collection d'attributs correspondant à une collection d'objets Personne. Il faut trois tables pour pouvoir mapper cette relation. Le fichier de mapping de la classe Personne s'écrit :


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<!-- Strategie table-per-class hierarchy mapping -->
<hibernate-mapping package="utilisateur5">
	 <class name="Personne" table="PERSONNES" discriminator-value="P">
	 	<id name="idPers" column="idPers" type="long">
	 		<generator class="sequence"/>
        </id>
        <discriminator column="sousclasse" type="character"/>
		<property name="nom" column="nom" type="string"/>
		<property name="prenom" column="prenom" type="string"/>
        <set  name="roles" table="PERSONNES_ROLES">
			<key column="idPers" />
			<many-to-many  class="utilisateur5.Role" column="idRole" />
	  </set>	
</hibernate-mapping>

La table PERSONNES_ROLES permet de faire la jointure entre la table PERSONNES et la tables ROLES. Le fichier de mapping de la classe Role s'écrit :


</class><?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
	

<hibernate-mapping package="utilisateur5">
	<class name="Role" table="ROLES" >
		<id name="idRole" column="idRole" type="long">
			<generator class="sequence" />
		</id>
		<property name="nom" column="nom" type="string"/>
		
		<set  name="personnes" table="PERSONNES_ROLES" inverse="true" >
			<key column="idRole" />
			<many-to-many  class="utilisateur5.Personne" column="idPers" >
		</set>	
	</class>
</hibernate-mapping>

5. Component mapping

personne

Exemple : Une personne possède un nom et un prénom.
On peut créer une classe Personne dont l'attribut nom est de type Name. Le fichier de mapping de la classe Personne s'écrit :


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<!-- Strategie table-per-class hierarchy mapping -->
<hibernate-mapping package="utilisateur5">
	 <class name="Personne" table="PERSONNES" discriminator-value="P">
	 	<id name="idPers" column="idPers" type="long">
	 		<generator class="sequence"/>
        </id>
        <discriminator column="sousclasse" type="character"/>
	  <property name="nom" column="nom" type="string"/>
	  <property name="prenom" column="prenom" type="string"/>
	   
	<component name="Name" class="utilisateur5.Name"> 
        <property name="nom"/>
        <property name="prenom"/>
      </component>
</hibernate-mapping>

6. Mapping de collections

Hibernate permet d'assurer la persistance des collections de type java.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List, et tous les tableaux d'entités et valeurs persistantes. Les propriétés de java.util.Collection ou java.util.List peuvent aussi être mappées avec la sémantique de Sac (Bag).

Manipulation des données

1. Chargement, Sauvegarde, Modification et Supression

Les données sont manipulées à partir d'une session créée à partir de la sessionFactory. Différentes actions sont possibles :
=> chargement des données : session.load()
=> Sauvegarde des données : session.save()
=> Chargement des données non sûres : session.get()
=> Suppression des données : session.delete()
=> Mise à jour des données : session.update()
Exemple de code de manipulation de données :


SessionFactory sessionFactory = cfg.buildSessionFactory();
	// ouverture de la session
	session = sessionFactory.openSession();
	tx = session.beginTransaction();
			
	// Récupérons l utilsateur de iduser = 25
	util1 = (Personne) session.get(Personne.class, new Long(25),LockMode.UPGRADE);
	// Modifions son password
	util1.setPassword("maow");
	// Sauvons l'enregistremment
	session.update(util1);
			
	// Suppression de l'utilisateur id = 26
	util1 = (Personne) session.load(Personne.class, new Long(26));
	session.delete(util1);
   tx.commit();

2. Les différentes Requêtes

Coexistant avec la manipulation implicite des données, trois langages de questionnement de la base de données sont gérés par Hibernate :
HQL : Hibernate Query Language
Critéria Queries
Native SQL Queries
Le HQL est conseillé dans la documentation Hibernate. L'exemple ci-dessous permet de récupérer l'ensemble des personnes.


Query q2 = session.createQuery("from Personne where idPers = 1");
pers = (Personne) q2.list().get(0);
user.setPers(pers);

Transaction et concurrence

1. Optimistic Control Concurrency

Le mode de contrôle optimiste de la concurrence laisse le soin à hibernate de gérer lui -même l'accès à la base de données. Pour une application distribuée, toutes les sessions doivent être créées à partir de la même instance de la session Factory.

2. Pessimistic Locking

Le mode de contrôle pessimiste laisse à l'utilisateur le soin de gérer les accès concurrents à la base de données. Il existe plusieurs mode de chargement des objets permettant de la manipuler en toute sécurité :

LockMode.WRITE est obtenu automatiquement lorsqu'Hibernate insère ou modifie un enregistrement.

LockMode.UPGRADE peut être obtenu à la demande explicite de l'utilsateur en utilisant la syntaxe SELECT ... FOR UPDATE sur les bases de données qui la supportent.

LockMode.READ est obtenu automatiquement lorsqu'Hibernate consulte des données avec des niveaux d'isolation de type lectures reproductibles (repeatable read) ou de type sérialisable (serializable). Peut être réobtenu à la demande explicite de l'utilsateur

LockMode.NONE représente l'absence de verrou. Tous les objets basculent à ce verrou à la fin d'une Transaction. Les objets associés à la session via l'appel de update() ou saveOrUpdate() démarrent aussi sur ce mode de verrou.

Exemple :


// Récupérons l utilsateur de iduser = 25
util1 = (Personne) session.get(Personne.class, new Long(25), LockMode.UPGRADE);

Liens

www.hibernate.org

http://stessy.developpez.com/j2ee/hibernate

Contact

contact