La persistance des données avec Hibernate 2.1.8

Romain Guy (Gfx)

De nombreuses applications ont besoin de pouvoir enregistrer d'importantes quantités de données. Les développeurs optent généralement pour une base de données, mais compliquent ainsi leur travail. Hibernate est conçu pour vous aider dans cette tâche. ♪

- Titre : La persistance des données avec Hibernate 2.1.8

- Auteur : Romain Guy (Gfx)

- Parution : 3 juillet 2006

- Licence : Cette création est mise à disposition sous un contrat Creative Commons (Paternité - Partage des Conditions Initiales à l'Identique).

Sommaire

- Introduction

- I. Mapping O/R

- II. Gestion de la persistance

- III. Screenshots

- IV. Liens

Introduction

Hibernate est une bibliothèque Java diffusée sous licence LGPL. Nous allons nous intéresser à la version 2.1.8 datant de janvier 2005. Cette bibliothèque est un service de mapping objet/relationnel (ou mapping O/R) pour la plateforme Java. Son rôle est donc de vous permettre de travailler efficacement avec le contenu d'une base de données relationnelle depuis votre code source Java. Le monde des SGBDR est très différent de celui de Java, qui s'appuie sur le paradigme objet. Les compétences nécessaires sont donc également différentes. Lorsqu'une application Java doit manipuler d'importantes quantités d'informations et doit pour cela réaliser une traduction entre la représentation relationnelle et la représentation objet des données.
Ce travail est délicat puisqu'un développeur Java risquera de ne pas comprendre parfaitement la couche de données et inversement. Néanmoins, quelques outils existent pour simplifier la tâche des développeurs. C'est ainsi que tous les accès aux bases de données en Java sont réalisés à travers des pilotes Java DataBase Connectivity (JDBC) dont l'API masque le type de SGBDR utilisé. Le second outil très employé est le design pattern Data Access Object ou DAO qui pousse encore plus loin le niveau d'abstraction proposé par JDBC.
Bien que suffisants pour permettre aux développeurs d'effectuer tous leurs travaux de gestion des données, ces outils sont limités et nécessitent beaucoup de travail, particulièrement lorsqu'une des structures, relationnelle ou objet, change. Hibernate vous permettra de transférer directement vos objets Java depuis vers une source de données. Cette approche est souvent opposée à celles proposées par les EJB (pour les versions 2.1 et inférieures). Ses défenseurs parlent d'ailleurs d'utiliser des Plain Old Java Object (POJO) en lieu et place des EJB. Le sujet des EJB et des POJO est très vaste, mais sachez simplement que vous pouvez parfaitement recourir à Hibernate pour sauvegarder les données des EJB Session ou pour réaliser la persistance des EJB Entité BMP.

I - Mapping O/R

Hibernate, en tant qu'outil de mapping O/R, doit vous permettre de synchroniser facilement le contenu d'une base de données avec votre application Java. Vous pouvez pour ce faire demander à Hibernate d'analyser une base de données existante pour générer les classes Java correspondantes. L'inverse est également possible. Vous obtiendrez alors les requêtes SQL nécessaires pour créer une base de données capable d'accueillir vos classes Java. Nous allons pour notre part commencer sans base de données ni classes et nous verrons comment créer un document de mapping Hibernate.
Avant d'écrire la moindre ligne de code, nous devons installer Hibernate. Pour ce faire, ouvrez l'archive hibernate-2.1.8.tar.gz pour en extraire hibernate2.jar, que nous avons placé dans notre exemple dans un sous répertoire lib/. Puisque cette bibliothèque n'agit que comme un agent de communication entre Java et une base de données, nous devons en installer une. Les développeurs d'Hibernate assurent un support officiel pour les SGBDR les plus connus : Oracle, MySQL, PostgreSQL, SQP DB, etc. La plupart nécessitent l'installation d'un serveur sur votre machine, avec tous les ennuis qui peuvent en découler. C'est pourquoi nous avons choisi HSQLDB, un SGBDR écrit en Java et disponible sous forme d'une simple archive JAR à intégrer à votre projet. Il nous suffit donc de copier hsqldb.jar dans notre dossier lib pour l'installer. Nous devons enfin créer une base de données vide dans HSQLDB. La procédure est particulièrement simple puisqu'il nous suffit de nous connecter à une base inexistante avec l'utilisateur sa, pour system administrator. Depuis le dossier du projet, exécutez la commande suivante :

$ java -classpath lib/hsqldb.jar org.hsqldb.util.DatabaseManager -driver org.hsqldb.jdbcDriver
-url jdbc:hsqldb:db/books -user sa

Cette commande fera apparaître le HSQL Database Manager et créera automatiquement la base intitulée books/ dans le dossier db/ du projet. Notez que l'archive hsqldb.jar contient le pilote JDBC que vous pouvez utiliser avec toute autre application compatible JDBC pour manipuler votre base de données HSQLDB. L'étape suivante concerne la rédaction du document de mapping O/R pour Hibernate. Il s'agit d'un fichier XML dans lequel nous pouvons réaliser les associations entre les membres d'une classe Java et les colonnes d'une table de la base de données. Le listing 1 présente le document BooksShelf.hbm dont l'extension hbm signifie Hibernate Mapping. Un document HBM peut contenir autant de déclarations de classes que vous le désirez. Dans cet exemple, l'attribut package de l'élément <hibernate-mapping /> est utilisé pour indiquer que toutes les classes déclarées appartiennent au même paquetage Java. Pour mélanger des paquetages, omettez cet attribut et donnez à chaque classe son nom complet. La déclaration d'une classe est réalisée à l'aide de l'élément <class /> auquel nous passons comme attributs son nom et sa table dans le SGBDR. L'attribut table n'est pas indispensable si vous souhaitez utiliser le même nom pour les deux entités. L'élément suivant, <meta />, n'est pas indispensable lors de la rédaction d'un document HBM, mais permet de le documenter. Les données inscrites dans ces éléments sont également ajoutées au code source Java que nous générerons à partir de ce fichier. Ainsi, un élément <meta /> de type class-description contient le commentaire JavaDoc qui sera inséré devant la déclaration de la classe Java. Les éléments restants définissent les membres de la classe et donc les colonnes de la table books. Le premier membre est un cas particulier faisant appel à l'élément <id />. Ce dernier sert à indiquer la clé primaire de la table. Nous l'utilisons ici pour déclarer le membre id correspondant à la colonne book_id. Son contenu présente une nouvelle utilisation intéressante de <meta />. Le type scope-set sert à indiquer la portée du mutateur, setId(), dans le code Java. Comme il est impensable de le laisser public, qui est la portée par défaut, nous changeons ici sa portée en protected. La clé primaire dispose enfin d'un générateur permettant de définir la manière dont elle est calculée. Le générateur natif est celui du SGBDR utilisé. Vous pouvez néanmoins créer votre propre générateur avec l'API Hibernate. Vous pourrez par exemple générer des clés en fonction de la date. Tous les autres membres de la classe Book sont définis avec l'élément <property /> dont les attributs sont parfaitement clairs.
Pour générer le code Java correspondant à ces définitions, nous devons installer les extensions Hibernate. Pour cela, ouvrez hibernate-extensions-2.1.3.tar.gz et copiez les fichiers hibernate-tools.jar, jdom.jar et velocity-1.3.1.jar dans notre dossier lib. Vous devez également copier les archives commons-*.jar et log4j-1.2.8.jar du dossier lib/ d'Hibernate. Après vous être assuré d'avoir placé le chemin de toutes ces archives dans votre variable d'environnement CLASSPATH, exécutez la commande suivante :

java -classpath lib/hibernate2.jar:$CLASSPATH net.sf.hibernate.tool.hbm2java.CodeGenerator
-output=src BooksShelf.hbm

Si la génération de code réussit, vous découvrirez un dossier intitulé src/ contenant le fichier com/loginmag/hibernate/Book.java. Le listing 2 présente un extrait de ce dernier. Pour compiler les fichiers générés, exécutez les commandes suivantes à la racine du projet :

$ mkdir bin
$ javac -classpath lib/commons-lang-1.0.1.jar -sourcepath src -d bin src/com/loginmag/hibernate/*.java

Le répertoire bin/ contient maintenant notre code Java compilé. Ce répertoire est important, car il doit également accueillir le fichier de configuration d'Hibernate. En effet, nous ne lui avons pas encore indiqué quel est le SGBDR à utiliser. Pour ce faire nous devons créer un fichier hibernate.properties à la racine de l'un des chemins du classpath. Le dossier bin/ contenant nos classes, le plus simple est d'y placer ce fichier dont le contenu se trouve dans le listing 3. Le fichier hibernate.properties définit le dialecte à utiliser, ici HSQLDialect. Un dialecte est une classe dont le rôle est de traduire les ordres SQL dans le langage propre à chaque SGBDR, car certaines différences peuvent exister. Les autres propriétés définissent la connexion à notre base de données books.
Maintenant qu'Hibernate peut se connecter à HSQLDB, nous pouvons lui demander de générer les tables définies dans le document HBM avec l'outil SchemaExport :

$ java -classpath hibernate2.jar:$CLASSPATH:lib/hsqldb.jar:bin
net.sf.hibernate.tool.hbm2ddl.SchemaExport BooksShelf.hbm

Le classpath utilisé pointe vers l'archive d'HSQLDB qui contient le pilote JDBC ainsi que vers le répertoire bin/ dans lequel se trouvent hibernate.properties et Book.class. L'outil SchemaExport nécessite donc le document HBM et la classe Java compilée. Par défaut, la transaction SQL est directement émise au SGBDR. Vous pouvez également produire un fichier SQL contenant l'ensemble des directives utilisées pour créer les tables en ajoutant les options --text et --output=fichier.sql avant le nom du fichier HBM sur la ligne de commande. Lancez enfin le HSQL Database Manager pour vérifier que la table books a bien été créée.

II - Gestion de la persistance

Une fois Hibernate et HSQLDB configurés, il ne nous reste plus qu'à écrire le code Java exploitant les mappings que nous avons définis. Le listing 4 présente le programme PersistData qui crée deux instances de Book et les sauvegarde avec Hibernate. Pour ce faire, nous devons ouvrir une transaction avec la couche de persistance des données. Un objet Transaction est obtenu à partir d'une session, elle-même créée depuis une configuration. Un objet Configuration peut charger des fichiers mappings de plusieurs manières. Dans notre cas, nous demandons la lecture explicite de BookShelf.hbm dans le répertoire d'exécution courant. Il est également possible de charger des HBM spécifiques à des classes en invoquant la méthode addClass(MaClasse.class). Dans ce cas, le fichier HBM doit s'appeler MaClass.hbm.xml et se trouver dans le même paquetage. Les transactions fonctionnent comme en SQL classique, avec la possibilité de faire des commit et des rollback. La persistance des POJO est réalisée en invoquant la méthode save() de la transaction. Vous pouvez les supprimer avec la méthode delete(). Après avoir compilé et exécuté ce programme, vous devez pour cela copier toutes les archives JAR de hibernate-2.1.8.tar.gz dans lib/, exécutez le Database Manager pour vérifier la présence des nouveaux objets.
Récupérer des POJO depuis la couche de persistance est tout aussi simple. Vous devez au préalable ajouter une <query /> au document HBM ainsi que le montre le listing 5. Les requêtes sont écrites dans un langage proche de SQL et propre à Hibernate. Notre requête se nomme books.booksByAuthor et dispose d'un paramètre appelé author. Le listing 6 présente comment l'exécuter et afficher les résultats. Après avoir obtenu l'objet Query à l'aide de son nom dans la session, nous pouvons fixer la valeur de son paramètre. Enfin, la requête est exécutée en invoquant la méthode list() qui renvoie une List contenant les instances de Book trouvées. Hibernate est donc une bibliothèque puissante et accessible que vous pourrez utiliser avec succès dans toutes vos applications Java.

listing 1

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >

<hibernate-mapping package="com.loginmag.hibernate">
<class name="Book" table="books">
<meta attribute="class-description">
Cette classe représente un livre appartenant à une collection.
</meta>

<id name="id" type="int" column="book_id">
<meta attribute="scope-set">protected</meta>
<meta attribute="field-description">
L'ISBN n'est pas utilisé comme identificateur unique afin de pouvoir
posséder plusieurs exemplaires d'un même ouvrage dans sa collection.
</meta>
<generator class="native" />
</id>

<property name="author" type="string" not-null="true" />

<property name="title" type="string" not-null="true" />

<property name="cover" type="string" not-null="true">
<meta attribute="field-description">
Contient le chemin vers une image de la couverture. Ce chemin peut
être une URL ou un chemin local.
</meta>
</property>

<property name="published" type="date">
<meta attribute="field-description">Date de publication</meta>
</property>
</class>
</hibernate-mapping>

listing 2

package com.loginmag.hibernate;

import java.io.Serializable;
import java.util.Date;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
* Cette classe représente un livre appartenant à une collection.
*/
public class Book implements Serializable {
/** identifier field */
private Integer id;

/** persistent field */
private String author;

/** persistent field */
private String title;

/** persistent field */
private String cover;

/** nullable persistent field */
private Date published;

/** full constructor */
public Book(String author, String title, String cover, Date published) {
this.author = author;
this.title = title;
this.cover = cover;
this.published = published;
}
// ...
}

listing 3

hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:db/books
hibernate.connection.username=sa
hibernate.connection.password=

listing 4

public class PersistData {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addFile(new File("BooksShelf.hbm"));

SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = null;

try {
transaction = session.beginTransaction();

Book book = new Book("Robert CALVET", "Les Japonais", "japonais.jpg");
session.save(book);

book = new Book("Terry PRATCHETT", "The Truth", "thetruth.jpg");
session.save(book);

transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
} finally {
session.close();
}

sessionFactory.close();
}
}

listing 5

<query name="books.booksByAuthor">
<![CDATA[from com.loginmag.hibernate.Book as book where book.author=:author]]>
</query>

listing 6

Query query = session.getNamedQuery("books.booksByAuthor");
query.setString("author", args[0]);
List results = query.list();
for (int i = 0; i < results.size(); i++)
System.out.println(((Book) results.get(i)).getTitle());

III - Screenshots

Le programme HSQL Database Manager est spartiate, mais permet de vérifier rapidement le contenu d'une base HSQLDB.

L'outil SchemaExport crée les tables dans la base de données à partir du document de mapping O/R.

L'ajout de données dans la base de données se fait en sauvegardant simplement les POJO dans une transaction.

HSQLDB est un SGBDR léger et performant distribué sous licence BSD sur le site hsqldb.sf.net.

Le site hibernate.org propose une documentation très complète.

Les JDO, une autre technologie de persistance des POJO, bénéficie du soutien de Sun Microsystems.

O'Reilly publie un excellent ouvrage sur Hibernate.

L'ouvrage Hibernate In Action est disponible sous forme d'eBook sur hibernate.org.

Bruce Tate, dans Better, Faster, Lighter Java, explique comment créer des applications J2EE légères à l'aide d'Hibernate.

JBoss met en avant les EJB 3.0 qui sont souvent opposés à des solutions comme Hibernate.

IV - Liens

- Les sources de l'application

- Le site officiel d'Hibernate

- La FAQ JDBC, JDO, Hibernate

- Débuter avec Hibernate sous Eclipse, par Julien Defaut

- JSF et Hibernate , par Jean-Louis Pasturel

- Critique de Hibernate 3.0 : Gestion optimale de la persistance dans les applications Java/J2EE, par Eric Siber